자바스크립트 동작 원리
싱글 스레드 (Single Thread)
자바스크립트는 싱글 스레드[1] 언어이다. 그러나, 자바스크립트의 특징들 중, 이와 상반되는 개념들이 여러 존재한다. 예를 들면, 비동기, 동시성, 논블로킹 입출력 등이 있다.
싱글 스레드인데 어떻게 동시성을 가질 수 있을까?
자바스크립트 런타임은 메모리 힙(Memory Heap)과 콜 스택(Call Stack)으로 구성되어 있다. 메모리 힙은 메모리 할당을 담당하며, 콜 스택은 코드가 호출되면서 스택으로 쌓이는 곳이다.
자바스크립트는 메인 스레드 하나로 이뤄진 싱글 스레드 언어이다. 하나의 메인 스레드에서 호출되는 함수들이 콜 스택에 쌓인다. 이 함수들은 LIFO(Last In First Out) 방식으로 실행된다. 자바스크립트가 싱글 스레드 기반 언어라는 것은 자바스크립트가 하나의 메인 스레드와 하나의 콜 스택을 갖고있다는 의미이다.
const foo = () => {
bar();
console.log('foo');
};
const bar = () => {
console.log('bar');
};
foo();
console.log('foo and bar');
위 함수들의 실행에 따른 출력 순서는 다음과 같다.
foo
실행foo
내부의bar
실행console.log('bar')
실행 후 콜 스택에서 제거bar
함수는 실행을 마쳤으므로 콜 스택에서 제거foo
함수로 돌아와서console.log('foo')
실행 후 콜 스택에서 제거foo
함수는 실행을 마쳤으므로 콜 스택에서 제거console.log('foo and bar')
콜 스택에 추가, 실행 후 제거
자바스크립트의 비동기 지원 여부
자바스크립트 런타임 자체에서 비동기 API를 지원하지 않는다. 동시성을 보장하는 비동기, 논블로킹 작업들은 자바스크립트 엔진을 구동하는 런타임 환경에서 담당한다. 이 런타임 환경은 브라우저가 될 수 있고, Node.js
가 될 수 있다.
자바스크립트 런타임 환경이 브라우저인 경우, 자바스크립트 구성 요소와 함께 이벤트 루프(Event Loop), 콜백 큐(Callback Queue) 등이 조합되어 있다. Web API도 확인할 수 있는데, 만약 런타임 환경이 Node.js
라면 Web API 대신 Node.js
가 지원하는 라이브러리 및 API가 될 수 있다.
브라우저 런타임의 구성 요소들의 수행 역할은 다음과 같다.
이벤트 루프
이벤트 발생 시 호출되는 콜백 함수들을 관리하여 콜백 큐에 전달한다. 콜백 큐에 담겨있는 콜백 함수들을 콜 스택에 넘겨준다.
이벤트 루프가 콜백 큐에서 콜 스택으로 콜백 함수를 넘겨주는 작업은 콜 스택에 쌓여있는 함수가 없을 때만 수행한다.
콜백 큐
Web API 에서 비동기 작업들이 실행된 후 호출되는 콜백 함수들이 기다리는 공간이다. 이벤트 루프가 정해준 순서대로 줄을 서있으며, FIFO(First In First Out) 방식을 따른다.
Web API
브라우저에서 자체 지원하는 API이다. 이는 DOM
이벤트, AJAX(XmlHttpRequest)
, setTimeout
등 비동기 작업을 수행할 수 있도록 API를 지원한다.
브라우저 런타임에서 비동기 코드가 실행되는 방식
Web API를 사용한 비동기 작업을 수행하는 코드는 아래 순서로 실행된다.
- 코드가 콜 스택에 쌓인 후 실행되면 자바스크립트 엔진은 비동기 작업을 Web API에게 위임한다.
- Web API는 해당 비동기 작업을 수행하고, 콜백 함수를 이벤트 루프를 통해 콜백 큐에 넘겨준다.
- 이벤트 루프는 콜 스택에 쌓여있는 함수가 없을 때, 콜백 큐에서 대기하는 콜백 함수를 콜 스택으로 넘겨준다.
- 콜 스택에 쌓인 콜백 함수가 실행된 후, 콜 스택에서 제거된다.
위와 같이 비동기 논블로킹 입출력 작업이 수행된다.
만약, 서버로부터 대용량의 리소스를 응답받아야 하는 HTTP
요청을 동기로 수행한다면 이 요청을 보내는 함수는 콜 스택에 쌓인 채로 머물게 된다. 즉, 자바스크립트 엔진은 이 함수의 실행이 끝날 때 까지 어떠한 작업도 수행할 수 없다. HTTP
요청으로 인해 다른 코드들을 블로킹하고 있는 것이다. 그러나 브라우저 런타임의 자바스크립트 엔진이 이 비동기 작업들을 Web API로 넘겨줌으로써, 해당 작업이 완료될 때 까지 다른 코드들을 실행할 수 있다. 이것이 논블로킹 입출력이다.
console.log('첫번째로 실행');
setTimeout(() => console.log('최소 1초 후에 실행됩니다.'), 1000);
console.log('나는 언제 실행될까요?');
위 코드의 출력 순서는 다음과 같다.
console.log('첫번째로 실행')
이 콜 스택에 쌓인다. 바로 실행되며, 실행 후 콜 스택에서 제거된다.setTimeout
이 콜 스택에 쌓인다. 이는 Web API로, 이 비동기 작업을 Web API로 넘긴다. Web API에서timer
가 생성된다.console.log('나는 언제 실행될까요?')
가 콜 스택에 쌓이고, 바로 실행된 후 콜 스택에서 제거된다.timer
는 생성된 시점을 기준으로 하여 1초 후에 콜백 큐로 콜백 함수를 전달한다.- 이벤트 루프가 콜 스택이 비어있는 것을 확인했고, 콜백 큐에 쌓인
setTimeout
의 콜백 함수를 콜 스택으로 넘기고 실행한다. console.log('최소 1초 후에 실행됩니다.')
가 실행된 후 콜 스택에서 제거된다.
만약, setTimeout
의 timer
를 0초로 설정하더라도 결과는 동일하다. 중요한 것은 시간이 아니라, setTimeout
이 Web API가 지원하는 비동기 함수라는 것이다.
setTimeout
의 콜백 함수는 바로 콜 스택에 쌓이지 않고 Web API에서 비동기 처리를 거친 후 콜백 함수가 콜백 큐로 전달된다. 시간이 0초여도 콜백 큐에 담긴 후 이벤트 루프가 콜 스택이 비어있는 것을 확인한 다음에야 콜백 함수가 콜 스택으로 넘겨지기 때문에 같은 결과가 나오는 것이다.
Notes
[1]싱글 스레드란 말 그대로 한 번에 하나의 작업만 수행할 수 있음을 의미한다. 다른 작업이 중간에 끼어들 수 없고, 기존에 수행하던 작업이 끝나야만 그 다음 작업을 수행할 수 있다.