[JS] JavaScript 비동기 심화: 이벤트 루프, 태스크 큐(=콜백 큐), 그리고 마이크로태스크

artp·2025년 9월 23일

javascript

목록 보기
35/50
post-thumbnail

JavaScript 비동기 심화: 이벤트 루프, 태스크 큐(=콜백 큐), 그리고 마이크로태스크

1. JavaScript 비동기 모델의 핵심 구성 요소

JavaScript는 '싱글 스레드' 언어이지만, 실행 환경(브라우저의 Web APIs, Node.js의 libuv) 덕분에 비동기 작업이 가능합니다. 이 모델은 다음 네 가지 핵심 요소로 구성됩니다.

  1. 콜 스택 (Call Stack)

    • 현재 실행 중인 함수가 쌓이는 LIFO(Last-In, First-Out) 공간입니다.
    • 동기 코드는 스택에 push되어 실행되고, 실행이 끝나면 pop되어 사라집니다.
    • 스택이 비어있지 않으면 다른 작업을 처리할 수 없습니다.
  2. 백그라운드 (Web APIs / libuv)

    • 시간이 오래 걸리는 작업을 처리하는 별도의 공간입니다. 브라우저에서는 Web APIs, Node.js에서는 libuv가 이 역할을 담당합니다.
    • setTimeout, fetch (네트워크 요청), 파일 I/O 등의 비동기 함수가 호출되면, 실제 작업은 이곳으로 위임됩니다.
    • 작업이 완료되면, 등록된 콜백 함수를 '태스크 큐'로 보낼 준비를 합니다.
  3. 태스크 큐 (Task Queue) = 콜백 큐 (Callback Queue)

    • 백그라운드에서 완료된 비동기 작업의 콜백 함수들이 대기하는 FIFO(First-In, First-Out) 공간입니다.
    • 이 큐는 매크로태스크 큐(Macrotask Queue)마이크로태스크 큐(Microtask Queue) 두 종류로 나뉩니다.
  4. 이벤트 루프 (Event Loop)

    • 콜 스택과 태스크 큐를 끊임없이 감시하는 역할입니다.
    • 콜 스택이 완전히 비워지면, 이벤트 루프는 태스크 큐에서 대기 중인 콜백을 하나 꺼내 콜 스택으로 옮겨 실행시킵니다. 이 과정을 '틱(tick)'이라고 합니다.

핵심 요약:
콜 스택에 함수가 쌓이고, 비동기 함수는 백그라운드로 작업을 위임합니다.
작업이 끝나면 콜백이 태스크 큐(= 콜백 큐)로 들어가고, 이벤트 루프는 콜 스택이 비었을 때 그 콜백을 스택에 올려 실행합니다.
이 구조 덕분에 싱글 스레드임에도 불구하고 논블로킹(Non-blocking) 작업이 가능합니다.

2. 시간 순으로 따라가보는 비동기 흐름

아래 코드를 통해 각 구성 요소가 어떻게 상호작용하는지 시간 순으로 분석해 보겠습니다.

console.log("시작"); // 1번

// 2번
setTimeout(() => {
  console.log("1000ms 후 실행");
}, 1000);

// 3번
setTimeout(() => {
  console.log("2000ms 후 실행");
}, 2000);

console.log("끝"); // 4번

스냅샷 타임라인

Step 1: console.log("시작") 실행

  • console.log("시작")이 콜 스택에 push -> 실행 -> pop 됩니다.
  • 출력: 시작
Call StackWeb APIsCallback Queue
console.log("시작")(비어있음)(비어있음)

Step 2 & 3: setTimeout 등록

  • setTimeout 함수들이 차례로 콜 스택에 push 됩니다.
  • 이 함수들은 타이머 작업을 Web APIs에 위임하고, 자신은 즉시 콜 스택에서 pop 됩니다.
  • Web APIs는 1000ms, 2000ms 타이머를 각각 카운트하기 시작합니다.
Call StackWeb APIsCallback Queue
setTimeoutTimer(1000ms), Timer(2000ms)(비어있음)

Step 4: console.log("끝") 실행

  • console.log("끝")이 콜 스택에 push -> 실행 -> pop 됩니다.
  • 출력:
  • 이제 모든 동기 코드가 실행되어 콜 스택은 완전히 비워집니다.
Call StackWeb APIsCallback Queue
(비어있음)Timer(1000ms), Timer(2000ms)(비어있음)

Step 5: 1000ms 경과 시점

  • Web APIs에서 1000ms 타이머가 만료되고, 등록된 콜백 함수를 콜백 큐로 보냅니다.
  • 이벤트 루프는 콜 스택이 비어있는 것을 확인하고, 큐에 있는 1000ms 콜백을 콜 스택으로 push 합니다.
  • 콜백이 실행되고 pop 됩니다.
  • 출력: 1000ms 후 실행
Call StackWeb APIsCallback Queue
() => { console.log(...) }Timer(2000ms)(비어있음)

Step 6: 2000ms 경과 시점

  • 동일한 방식으로 2000ms 타이머가 만료 -> 콜백이 큐로 이동 -> 이벤트 루프가 스택으로 push -> 실행 -> pop.
  • 출력: 2000ms 후 실행

3. 심화 개념

3.1. setTimeout의 지연 시간은 '최소' 보장 시간이다

setTimeout(callback, 1000)은 "정확히 1000ms 후에 콜백을 실행하라"는 의미가 아닙니다. "최소 1000ms가 지난 후에 콜백을 큐에 넣어라"는 의미입니다.

만약 콜 스택에 오래 실행되는 동기 코드가 있다면, 1000ms가 지나 콜백이 큐에 도착했더라도 콜 스택이 비워질 때까지 계속 대기해야 합니다.

3.2. 마이크로태스크(Microtask) vs 매크로태스크(Macrotask)

태스크 큐는 실제로는 두 종류로 나뉘며, 마이크로태스크 큐가 항상 우선순위가 높습니다.

  • 매크로태스크 (Macrotask): setTimeout, setInterval, setImmediate, I/O 작업, UI 렌더링 등. 일반적인 '태스크 큐'를 의미합니다.
  • 마이크로태스크 (Microtask): Promise.then/catch/finally, process.nextTick (Node.js), queueMicrotask 등.

이벤트 루프의 정확한 실행 순서:
1. 콜 스택에서 하나의 매크로태스크를 실행합니다. (최초 실행 시에는 전역 코드)
2. 콜 스택이 비워지면, 마이크로태스크 큐에 있는 모든 작업을 전부 실행합니다.
3. 하나의 매크로태스크를 큐에서 꺼내와 실행합니다.
4. 다시 2번으로 돌아가 반복합니다.

실행 순서 비교 예제

console.log("1. 동기 코드 시작");

// 매크로태스크 큐에 등록
setTimeout(() => {
  console.log("5. setTimeout 콜백 (매크로태스크)");
}, 0);

// 마이크로태스크 큐에 등록
Promise.resolve().then(() => {
  console.log("3. Promise 콜백 (마이크로태스크)");
});

// 마이크로태스크 큐에 등록
queueMicrotask(() => {
    console.log("4. queueMicrotask 콜백 (마이크로태스크)");
});

console.log("2. 동기 코드 끝");

출력 결과:

1. 동기 코드 시작
2. 동기 코드 끝
3. Promise 콜백 (마이크로태스크)
4. queueMicrotask 콜백 (마이크로태스크)
5. setTimeout 콜백 (매크로태스크)

해설:
1. 동기 코드(1, 2)가 모두 실행되어 콜 스택이 비워집니다.
2. 이벤트 루프는 매크로태스크 큐를 보기 전에 마이크로태스크 큐를 먼저 확인합니다.
3. 마이크로태스크 큐에 있는 PromisequeueMicrotask의 콜백(3, 4)을 순서대로 모두 실행합니다.
4. 마이크로태스크 큐가 비워진 것을 확인한 후, 이제 매크로태스크 큐를 확인하여 setTimeout의 콜백(5)을 실행합니다.

4. 정리

  • 기본 흐름: 동기 코드 실행 -> 이벤트 루프가 큐 확인 -> 큐의 콜백을 스택으로 이동 후 실행
  • 정확한 흐름: 하나의 매크로태스크 실행 -> 마이크로태스크 큐 전체 비우기 -> (필요시 렌더링) -> 다음 매크로태스크 실행
  • setTimeout의 지연 시간은 최소 보장 시간이며, 실제 실행은 콜 스택과 마이크로태스크 큐의 상태에 따라 달라집니다.
profile
donggyun_ee

0개의 댓글