본문 바로가기

Language/JavaScript

<이벤트루프> 태스크 큐 : 마이크로태스크 큐 & 매크로태스크 큐

 

1. 소개

* 소개

 - 지난 장에서 봤듯이 비동기 작업은 태스크큐에서 대기하다가 콜스택으로 넘어가서 실행된다.

 

 - 그렇다면 여러개의 비동기 작업이 들어가면 어떻게 될지 알아보자.

 

 - 아래에서 사용되는 이미지와 코드는 이전 포스트의 연장선이므로 이전 포스트를 보지 않았다면 보는 것을 권장한다.

 

2. 실행

* 태스크 큐

 - 먼저 동일한 비동기 동작이 여러 개가 있을 경우를 살펴보자.

 

function myFunc1() {
  Promise.resolve().then(() => {
    console.log("promise1");
  });
  myFunc2();
  Promise.resolve().then(() => {
    console.log("promise3");
  });
  console.log("bye");
}

function myFunc2() {
  Promise.resolve().then(() => {
    console.log("promise2");
  });
  console.log("hello");
}

myFunc1();

 

 - 지난 장에서 봤던 코드에 즉시 실행되는 promise를 3개 삽입해두었다. 모든 비동기 API는 작업이 완료되면 콜백 함수가 태스크 큐에 추가된다. 즉, "hello" -> "bye" 까지는 예측이 가능한데, 태스크 큐에 추가된 각 promise는 어떤 순서일까.

 

 - 태스크 큐라는 이름에서 예상가능하듯이 선입선출로 실행된다. "hello" -> "bye" -> "promise1" -> "promise2" -> "promise3"

 - 이벤트 루프에서 이들이 어떻게 동작하고 있는지 확인해보자.

 

 

 - 콜백함수가 태스크 큐에 순서대로 추가되고, 콜스택이 모두 비워지면 먼저 들어왔던 "promise1" 이 콜스택으로 들어간다.

 

 - 이 때, 마찬가지로 콜스택이 비워지면 태스크 큐에 먼저 들어왔던 "promise2" 가 콜스택으로 들어간다.

 

 - 이번엔 타이머 함수를 살펴보자.

 

 - 만일 1초후실행되는 함수와 0초후 실행되는 함수가 순서대로 들어간다면 이벤트 루프는 이를 어떻게 처리할까?

 

function myFunc1() {
  setTimeout(() => {
    console.log("time 1sec");
  }, 1000);
  myFunc2();
  setTimeout(() => {
    console.log("time 0sec");
  }, 0);
  console.log("bye");
}

function myFunc2() {
  console.log("hello");
}

myFunc1();

 

 - 위의 코드가 실행된다면 "time 1sec"이 먼저 들어갔으니 "time 1sec" 이 찍히고 0초를 센 후 "time 0sec" 이 실행될까? 결론부터 말하자면 그렇지 않다. 사실 이렇게 동작하면 동시성이 있는 작업이라고 할 수도 없을 것이다.

 

 - 위의 결과는 "hello" -> "bye" -> "time 0sec" -> "time 1sec" 이다. 당연히 이렇게 되어야한다고 생각은하지만 어떻게 이벤트 루프가 처리되는 것인지 모를 수도있다. 아래의 이미지를 확인하자.

 

 

 - 이 때 동작하는 것이 Background, 즉 웹API 또는 노드API이다. 브라우저나 OS단에서 계산을 하게된다. 

 

 - 그동안 "time0" 은 계산이 끝나 태스크큐로 먼저 넘어간다. 이후로는 이미 알다시피 선입선출의 순서로 처리되는 것이다.

 

 

* 마이크로 태스크 큐와 매크로 태스크 큐

 - 이제 마지막으로 프라미스와 타이머 함수가 들어간 코드를 살펴보자.

 

function myFunc1() {
  setTimeout(() => {
    console.log("time");
  }, 0);
  myFunc2();
  Promise.resolve().then(() => {
    console.log("promise");
  });
  console.log("bye");
}

function myFunc2() {
  console.log("hello");
}

myFunc1();

 

 - 위 코드의 순서를 예측해보라. 지금까지 봤던 개념대로 예상해보면 "time" 후에 "promise"가 찍힐 것처럼 느껴진다.

 

 - 하지만 놀랍게도 순서는 "hello" -> "bye" -> "promise" -> "time" 이다. 대체 왜 그럴까. 백그라운드가 사실 0초를 0.1초라고 계산이라도 하는 것일까. 

 

 - 이는 사실 태스크 큐가 우선순위를 갖고 있기 때문이다. 위의 그림까지는 태스크큐를 하나로 두었지만 실제로는 마이크로 태스크(micro task)와 매크로 태스크(macro task)를 분리하여 처리한다. 참고로 매크로 태스크는 일반 태스크로 불리기도 한다.

 

 - 마이크로 태스크 큐는 매크로 태스크 큐에 비해 우선순위가 높아서, 콜 스택이 비워지면 마이크로 태스크 큐를 먼저 확인하게 된다. 모든 마이크로 태스크 큐가 비워지면 매크로 태스크 큐를 실행한다. 이러한 과정을 위 코드와 아래 이미지를 통해 확인해보자.

 

 - 타이머 콜백은 매크로태스크큐에, 프라미스 콜백은 마이크로 태스크큐에 들어갔음을 확인할 수 있다. 콜스택이 비워지자 마이크로태스크 큐에서 작업을 먼저 꺼낸다.

 

* 분류

 - 콜백 함수를 매크로 태스크 큐로 넣는 함수들은 다음과 같다.

  ∙ setTimeout

  ∙ setInterval

  ∙ setImmediate

  ∙ I/O

  ∙ UI 렌더링

 

- 콜백 함수를 마이크로 태스크 큐로 넣는 함수들은 다음과 같다

  ∙ Promise

  ∙ process.nextTick

  ∙ Object.observe

  ∙ MutationObserver

 

- 참고로 브라우저의 requestAnimationFrame 와 같은 애니메이션의 경우 Animation Callback Queue에서 담당한다. 위와는 별개로 픽셀의 리페인트 이전에 동작하며, 애니메이션 작업중에 들어온 추가적인 작업들은 다음 프레임으로 미루는 방식이다.

 


참고

 

 

 

자바스크립트와 이벤트 루프 : NHN Cloud Meetup

자바스크립트와 이벤트 루프

meetup.toast.com

 

[JS] Event Loop : 마이크로태스크(Microtask)와 매크로태스크(Macrotask) 알아보기

Microtask Queue를 알아 보자🔄

velog.io

 

 

What is the difference between the event loop in JavaScript and async non-blocking I/O in Node.js?

In this answer to the question - What is non-blocking or asynchronous I/O in Node.js? the description sounds no different from the event loop in vanilla js. Is there a difference between the two?...

stackoverflow.com

 

'Language > JavaScript' 카테고리의 다른 글

<ES10> ES2019 Features  (0) 2021.10.11
<ES9> ES2018 Features  (0) 2021.10.10
<이벤트루프> JS와 이벤트루프  (2) 2021.10.03
<ES8> ES2017 Features  (0) 2021.09.26
<ES7> ES2016(ES7) Features  (0) 2021.08.07