이번 글은 Event Loop (이벤트 루프) 에 대해 정리해보려고 한다.

Event Loop?

출처 How JavaScript works: an overview of the engine, the runtime, and the call stack

Event Loop는 MDN 문서로 검색하면 "큐의 다음 메시지를 처리합니다" 라고 나온다.
이것만 봐서는 모르겠다.... 하나씩 정리해보자.

JS Engine

자바스크립트 엔진은 Memory HeapCall Stack 으로 구성되어 있다.(그림 왼쪽!)
가장 유명한 것이 구글의 V8 Engine이다.
자바스크립트는 단일 스레드 (sigle thread) 프로그래밍 언어인데,
이 의미는 Call Stack이 하나 라는 이야기이다.
(멀티가 되지 않고, 하나씩 하나씩 처리한다는 의미!)

  • Memory Heap : 메모리 할당이 일어나는 곳
    (ex, 우리가 프로그램에 선언한 변수, 함수 등이 담겨져 있음)

  • Call Stack : 코드가 실행될 때 쌓이는 곳. stack 형태로 쌓임.

    • Stack(스택) : 자료구조 중 하나, 선입후출(LIFO, Last In First Out)의 룰을 따른다.

Web API

그림의 오른쪽에 있는 Wep API는 JS Engine의 밖에 그려져 있다.
즉, 자바스크립트 엔진이 아니다.
Web API브라우저에서 제공하는 API 로, DOM, Ajax, Timeout 등이 있다.
Call Stack에서 실행된 비동기 함수는 Web API를 호출하고,
Web API는 콜백함수를 Callback Queue에 밀어 넣는다.

Callback Queue

비동기적으로 실행된 콜백함수가 보관 되는 영역이다.
예를 들어 setTimeout에서 타이머 완료 후 실행되는 함수(1st 인자),
addEventListener에서 click 이벤트가 발생했을 때 실행되는 함수(2nd 인자) 등이 보관된다.

  • Queue(큐) : 자료 구조 중 하나, 선입선출(FIFO, Frist In Frist OUT)의 룰을 따른다.

Event Loop

Event Loop는 Call Stack과 Callback Queue의 상태를 체크하여,
Call Stack이 빈 상태가 되면, Callback Queue의 첫번째 콜백을 Call Stack으로 밀어넣는다.
이러한 반복적인 행동을 틱(tick) 이라 부른다.

정리하면,

  • V8 엔진에서 코드가 실행되면, Call Stack에 쌓인다.
  • Stack의 선입후출의 룰에 따라 제일 마지막에 들어온 함수가 먼저 실행되며,
    Stack에 쌓여진 함수가 모두 실행된다.
    • 비동기함수가 실행된다면, Web API가 호출된다.
    • Web API는 비동기함수의 콜백함수를 Callback Queue에 밀어넣는다.
    • Event Loop는 Call Stack이 빈 상태가 되면
      Callback Queue에 있는 첫번째 콜백을 Call Stack으로 이동시킨다.
      (이러한 반복적인 행동을 틱(tick)이라 한다.)

자바스크립트를 단일 스레드 프로그래밍 언어라 한번에 하나씩 밖에 실행할 수 없다.
그러나 Web API, Callback Queue, Event Loop 덕분에 멀티 스레드 처럼 보여진다.
👍

Microtask Queue???

출처 자바스크립트 비동기 처리 과정과 RxJS Scheduler

겨우 Event Loop를 조금.. 이해한 것 같은데...
Event Loop가 설명된 또 다른 그림에서 내가 모르는 개념이 보인다...하..

먼저 코드로 간단히 알아보자.

console.log('script start'); 

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

콘솔창에는 어떤 순서로 실행될까?

script start
script end
promise1
promise2
setTimeout

script start, script end 순으로 처리되는건 당연히 알겠는데..
왜 Promise, setTimeout 순으로 실행되는 걸까?

여기서 Microtask Queue의 개념이 나온다.

Event Loop는 우선적으로 Microtask Queue를 확인한다.
만약 Microtask Queue에 콜백이 있다면, 이를 먼저 Call Stack에 담는다.
그리고 Microtask Queue에 더이상 처리해야할 콜백이 없다면,
Task Queue에 확인 후 처리한다.

Promise의 then()의 콜백 은 Task Queue가 아닌 Microtask Queue에 담긴다.
따라서 위 코드에서는 우선순위가 높은 Microtask Queue부터 처리되므로,
Promise의 then() 콜백이 다 실행되고, setTimeout 콜백이 실행되는 거다.

(맨 위 그림에서 Callbakc Queue가 너무 단순하게 처리되어있었다...)

Animation Frames????

산넘어 산이다....ㅠㅠㅠㅠ
Animation Frames이란 개념도 있다.
requestAnimationFrame API가 실행되면 콜백이 Animation Frames으로 담긴다.
(setTimeout이 실행되면 타이머 완료 후 콜백이 Task Queue에 담기는 것 처럼...)

그럼 Microtask Queue, Task Queue, Animation Frames의 우선순위는 어찌될까?
코드로 확인해보자.

console.log("script start");

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

Promise.resolve().then(function() {
  console.log("promise1");
}).then(function() {
  console.log("promise2");
});

requestAnimationFrame(function() {
    console.log("requestAnimationFrame");
})
console.log("script end");

콘솔 창에 위의 코드를 실행시키면,

script start
script end
promise1
promise2
requestAnimationFrame
setTimeout
  • Microtask Queue > Animation Frames > Task Queue 순으로 실행된다.
    (크롬 기준이다! 브라우저마다 다를 수 있다.)

다시 정리해보면,

  • 코드가 실행되면 Call Stack에 쌓이고, Stack에서는 선입후출 룰 대로 실행된다.
    • 비동기 함수가 실행된다면, Web API가 호출된다.
    • Web API는 비동기함수의 콜백함수를 Callback Queue에 밀어넣는다.
      • Promise는 Microtask Queue로, Timeout은 Task Queue로,
        RequestAnimationFrame은 Animation Frame으로 콜백함수를 밀어넣는다.
    • Event Loop는 Call Stack이 빈 상태가 되면 콜백을 Call Stack으로 이동시킨다.
      • 콜백 이동 우선순위는 Microtask Queue > Animation Frames > Task Queue 이다.

실제로 실행해보면, seTimeout과 requestAnimationFrame의 순서가 바뀌는 결과도 있었다..
분명 우선순위는 Animation Frames이 Task Queue보다 높다고 하는데 왜 그럴까?ㅠㅠ
구글링을 통해 내가 추측한 바로는... (정말 추측이다. 정확히 모르겠다.. 알려주세요..ㅠㅠ)

브라우저는 좋은 사용자 경험을 위해 초당 60번 화면을 레더링 한다고 한다.
이를 위해서는 16ms안에 1번 화면을 그려야 한다.

만약 Microtask Queue안에 있던 콜백은 다 Call Stack으로 이동되었고,
Animation Frames과 Task Queue에 모두 콜백이 있다고 가정 하자..
만약 Task Queue의 콜백이 먼저 Call Stack으로 이동되어도
16ms 안에 1번 렌더링할 수 있다면, Task Queue의 콜백이 먼저 이동되는거 아닐까?

Microtask Queue의 콜백을 많이 만들어서,
일정 시간동안 Animatation Frame과 Task Queue의 콜백이 그대로 쌓여있을 경우
Microtask Queue의 콜백이 모두 Call Stack으로 이동되었을 때,
( = Microtask Queue가 빈 상태가 될 경우)
Animation Frames에 쌓여있던 Callback이 모두 다 Stack으로 이동되어야
Task Queue에 쌓여있던 콜백이 이동한다.

즉, Animation Frames가 Task Queue보다 우선순위가 높게 작용했다.
[자세한건 7번째 링크 - 비동기 스케줄링과 Frame의 LifeCycle 확인해보세요.]


Event Queue?? Job Queue??

마지막으로 Event Queue, Job Queue라는 개념도 나온다.
Event Queue는 Task Queue와 동일한 역할을,
Job Queue는 Microtask Queue와 동일한 역할을 가지고 있다.
(자세한 내용은 하기 링크를 참고!!)

[출처] JavaScript main thread. Dissected.

Render Queue???

진짜 마지막으로...(ㅋㅋㅋㅋ) Render Queue라는 개념도 있다..
(자세한건 출처링크에서 확인!!)
[출처] Browser Rendering Queue in-depth

  • Render Queue는 브라우저에서 사용자에게 래스터 이미지를 보여주기 위해
    HTML, CSS, Javascirpt 코드를 변환하는 과정을 의미한다.
    (rendering path 혹은 critical rendering path 라고도 한다.)

  • DOM Tree -> CSS Tree -> CSSOM -> Render Tree -> Layouting
    -> Layer Tree -> Paint -> GPU Syne -> Composition 순으로 이뤄지는데,
    Composition은 스크린에 그려진 Final Frame이다.


여기서 Animation Frames와 Render Queue를 같이 설명된 자료는 찾을 수 없었다...ㅠㅠ

역시 내 추측이지만...ㅠㅠ
Render Queue에 만들어진 Composition도 Event Loop에 의해 우선순위가 결정되고,
Animation Frames에 에 담긴 Callback도 Event Loop에 의해 우선순위가 결정된다.
Render Queue와 Animation Frames은 서로 같은 개념 아닐까?


참고

profile
꾸준히, 끄적끄적 해볼게요 :)

3개의 댓글

comment-user-thumbnail
2020년 4월 5일

좋은 글 이네요!
보기쉽게 이해하기 쉽게 정리 된 글 인것 같습니다!

답글 달기
comment-user-thumbnail
2020년 5월 5일

저같은 초짜가 봐도 읽힐 정도로 기초적인 개념들도 잘 설명해주셔서 감사해요. 너무 유익한 글이네용!ㅎㅎ

답글 달기
comment-user-thumbnail
2020년 9월 18일

잘 읽고 갑니다

답글 달기