본문 바로가기

Javascript Concepts

Message Queue and Event Loop

Runtime concepts


https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop/the_javascript_runtime_environment_example.svg

 

이벤트 루프를 이해하기 위해서는 먼저 자바스크립트의 런타임 환경에 대해서 알아야 한다. 위 그림에서 볼 수 있듯이 자바스크립트 런타임은 스택(Stack), 힙(Heap), 그리고 큐(Queue) 를 사용한다.

Stack

함수를 호출하면 함수 프레임이 스택에 쌓인다. 스택이라는 이름에서 알 수 있듯이 스택의 가장 위에 있는 함수부터 실행되며, 함수가 끝나면 스택에서 나간다(pop). 크롬 개발자 도구의 콘솔에서 다음 코드를 실행시켜보자.

function foo() {
    bar();
}

function bar() {
    baz();
}

function baz() {
    console.log("baz");
    debugger;
}

foo();

baz()가 끝나기 전의 콜스택에 foo(), bar(), baz() 가 순서대로 쌓여있는 모습을 확인할 수 있다.

Heap

힙은 객체들이 할당되는 곳이다.

Queue

A JavaScript runtime uses a message queue, which is a list of messages to be processed. Each message has an associated function which gets called in order to handle the message. (MDN)

 

메세지 큐(Message Queue) 는 처리되어야 할 메세지의 리스트이다. 각 메세지는 메세지를 처리할 때 호출되는 함수를 가지고 있다. 이 메세지를 처리하기 위해서 필요한 것이 이벤트 루프(Event Loop)이다.

What is an Event Loop?


https://www.techboxweb.com/wp-content/uploads/2019/12/event-loop-in-javascript-1024x1024.png

 

이벤트 루프는 자바스크립트의 비동기 프로그래밍을 가능하게 해준다. 다시 말해, 자바스크립트가 실제로는 싱글 스레드(single thread) 에서 동작함에도 마치 여러 개의 스레드에서 동작하는 것처럼 보이게 해준다.

How does it work?

위 그림에서 볼 수 있듯이, 비동기 함수가 호출되면 이 함수는 즉시 콜스택에서 나가 Web API로 보내진다. Web API 는 자신만의 스레드에서 전달 받은 함수를 실행하고, 실행이 끝나면 그 함수가 끝났을 때 실행되어야 할(보통 인자로 같이 넘겨 받는다) 콜백함수를 메세지 큐에 넣는다. 이벤트 루프는 콜 스택을 계속 관찰하다가 콜스택이 빈 것을 확인하면 메세지 큐에서 콜백함수를 꺼내 스택에 넣는다.

setTimeout() 함수를 예로 들어보자. 아래의 setTimeout() 함수가 호출되면 이는 즉시 Web API 로 보내진다. 그리고 WebAPI는 setTimeout()의 인자로 넘겨받은 시간동안 기다렸다가 콜백 함수를 메세지 큐에 넣는다. 그리고 이벤트 루프는 콜스택이 비면 해당 콜백함수를 스택에 넣을 것이다.

setTimeout(() => console.log("I'm callback"), 5000);
console.log("Hello!");
// "Hello!" 출력
// 약 5초 뒤 "I'm callback" 출력

따라서 setTimeout() 함수는 정확한 시간 뒤에 콜백함수가 실행되는 것을 보장해주지 않는다. 만약 콜스택이 비어있지 않다면 콜백함수는 영영 실행되지 못할 것이다.

setTimeout(() => console.log("I'm callback"), 5000);
while(1);
// while 루프가 스택을 점유하고 있기 때문에 콜백함수는 실행되지 못한다.

이렇게 콜스택이 비어있을 때만 메세지 큐의 콜백함수를 스택에 넣기 때문에, 메세지 처리가 완료될 때까지 다른 메세지는 실행되지 않는다. 다시 말하면 어떤 함수가 실행되는 중간에 다른 함수에 의해 선점당하지 않는다.

Message Queue


지금까지는 이벤트 루프의 역할을 이해하기 위해 메세지 큐가 마치 하나인 것처럼 설명했지만 사실 메세지 큐는 하나가 아니다(!) 메세지 큐는 여러 개가 있으며, 우선 순위에 따라 실행된다.

https://cdn.sanity.io/images/1yqdi3pp/production/d0ddc8f5d4d6388aac9b71e66c085eafaf492630-800x800.png

  • Microtask Queue - Promisethen 으로 넘겨준 등록된다. 우선적으로 처리되는 큐로, 큐에 있는 모든 콜백들이 실행된다.
  • Render Queue - requestAnimationFrame() 으로 넘겨준 콜백이 등록된다. Microtask Queue 와 마찬가지로 모든 콜백들이 실행되고 나면 브라우저 렌더링이 진행된다.
  • Task Queue - setTimeout() 이나 AJAX API 를 통해 넘겨준 콜백이 등록된다. 하나의 콜백만 실행된다.

다음의 코드를 실행시켜보자. 동기 함수인 console.log("hello") 가 가장 먼저 실행되고, 프로미스의 then 으로 체이닝된 콜백들, requestAnimationFrame() 으로 등록한 콜백들, 마지막으로 setTimeout()으로 등록한 콜백이 순서대로 실행되는 것을 확인할 수 있다.

setTimeout(() => {console.log("setTimeout")}, 0);

const promise = new Promise((resolve, reject) => {
    resolve("promise");
})

requestAnimationFrame(() => {
    console.log("request animation frame1;")
})

promise.then((result) => console.log(result))
    .then(() => console.log("then1"))
    .then(() => console.log("then2"));

requestAnimationFrame(() => {
    console.log("request animation frame2;")
})

console.log("hello!");
// 출력

hello!
promise
then1
then2
request animation frame1;
request animation frame2;
setTimeout

 

 


References

What the heck is the event loop anyway? | Philip Roberts | JSConf EU

What is an event loop in JavaScript?

Concurrency model and the event loop - JavaScript | MDN

'Javascript Concepts' 카테고리의 다른 글

RequestAnimationFrame  (0) 2021.10.13
IIFE  (0) 2021.10.01
Scope  (0) 2021.09.28
ES Modules  (0) 2021.09.27