이벤트 루프와 태스크 스케줄링 (Event Loop and Task Scheduling)
자바스크립트에서 비동기 작업이 어떻게 실행되는지 이해하려면 이벤트 루프와 태스크 스케줄링을 알아야 한다. 이 두 개념이 자바스크립트 엔진의 핵심 동작을 좌우한다. 이번에는 이벤트 루프의 기본부터 태스크와 마이크로태스크의 스케줄링까지 코드와 함께 깊이 파고들어보려고 한다.
단계별로 하나씩 뜯어보면서 자바스크립트의 비동기 흐름을 명확히 파악해보자.
이벤트 루프 기본
이벤트 루프는 자바스크립트가 단일 스레드 환경에서 비동기 작업을 처리할 수 있게 해준다. 간단한 동작부터 보자:
console.log("시작");
setTimeout(() => {
console.log("타이머");
}, 1000);
console.log("끝");
// "시작"
// "끝"
// 1초 후 "타이머"
setTimeout
은 비동기 작업으로 태스크 큐에 들어가고, 이벤트 루프가 이를 나중에 실행한다. 호출 스택이 비면 태스크 큐를 확인해서 실행한다.
1. 호출 스택과 태스크 큐
호출 스택과 태스크 큐의 관계를 이해해야 한다:
console.log("1");
setTimeout(() => console.log("2"), 0);
console.log("3");
// "1"
// "3"
// "2"
여기서 setTimeout
의 콜백은 태스크 큐로 이동하고, 호출 스택이 비워진 후에 이벤트 루프가 태스크 큐에서 꺼내 실행한다. 0초라도 바로 실행되지 않는다.
2. 마이크로태스크와의 차이
태스크 외에 마이크로태스크라는 게 있다. Promise
는 마이크로태스크 큐로 간다:
console.log("시작");
setTimeout(() => console.log("타이머"), 0);
Promise.resolve("프로미스").then(value => console.log(value));
console.log("끝");
// "시작"
// "끝"
// "프로미스"
// "타이머"
마이크로태스크는 호출 스택이 비면 바로 실행되고, 태스크는 그 다음에 처리된다. 이벤트 루프는 먼저 마이크로태스크 큐를 비우고 나서 태스크 큐로 넘어간다.
3. 복잡한 태스크 스케줄링
여러 비동기 작업이 얽히면 어떻게 될까:
console.log("1");
setTimeout(() => {
console.log("2");
Promise.resolve("3").then(value => console.log(value));
}, 0);
setTimeout(() => console.log("4"), 0);
Promise.resolve("5").then(value => console.log(value));
console.log("6");
// "1"
// "6"
// "5"
// "2"
// "3"
// "4"
호출 스택이 비면 마이크로태스크("5")가 먼저 실행되고, 그 다음 태스크 큐에서 순서대로 "2", "4"가 처리된다. "2" 안의 프로미스는 다시 마이크로태스크로 들어가 "3"을 출력한다.
4. 태스크 지연 이해
태스크가 지연되는 상황을 보자:
function block() {
let start = Date.now();
while (Date.now() - start < 2000);
}
console.log("시작");
setTimeout(() => console.log("타이머"), 0);
block();
console.log("끝");
// "시작"
// 약 2초 후 "끝"
// 바로 "타이머"
호출 스택이 block
으로 막히면 태스크 큐의 "타이머"는 스택이 비워질 때까지 기다린다. 이벤트 루프는 스택이 비어야 동작한다.
5. 마이크로태스크 과부하
마이크로태스크가 쌓이면 어떻게 될까:
Promise.resolve()
.then(() => console.log("1"))
.then(() => console.log("2"))
.then(() => console.log("3"));
console.log("4");
// "4"
// "1"
// "2"
// "3"
마이크로태스크는 한 번에 모두 처리된다. 호출 스택이 비면 마이크로태스크 큐를 끝까지 비운다.
6. 브라우저 렌더링과의 관계
이벤트 루프는 렌더링과도 연결된다:
function heavyTask() {
let i = 0;
while (i < 10000000) i++;
console.log("무거운 작업 끝");
}
setTimeout(() => console.log("타이머"), 0);
Promise.resolve().then(() => console.log("프로미스"));
heavyTask();
// 약간의 지연 후 "무거운 작업 끝"
// "프로미스"
// "타이머"
무거운 작업이 스택을 막으면 렌더링도 지연된다. 브라우저는 이벤트 루프 한 사이클이 끝나야 화면을 갱신한다.
7. 태스크 분할로 부하 줄이기
긴 작업을 쪼개서 태스크로 나눌 수 있다:
function splitTask(total, chunkSize) {
let count = 0;
function step() {
let start = count;
while (count - start < chunkSize && count < total) {
count++;
}
console.log(`진행: ${count}/${total}`);
if (count < total) {
setTimeout(step, 0);
}
}
step();
}
splitTask(100, 20);
// "진행: 20/100"
// "진행: 40/100"
// "진행: 60/100"
// "진행: 80/100"
// "진행: 100/100"
작업을 태스크로 나눠 이벤트 루프가 다른 일을 처리할 틈을 준다.
8. 실제 API 호출 스케줄링
API 호출을 스케줄링해보자:
async function fetchData(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
const data = await response.json();
return data.title;
}
console.log("시작");
setTimeout(() => console.log("타이머"), 0);
fetchData(1).then(title => console.log("API: " + title));
console.log("끝");
// "시작"
// "끝"
// "API: sunt aut facere repellat provident occaecati excepturi optio reprehenderit"
// "타이머"
API 호출은 마이크로태스크로 처리되고, setTimeout
은 태스크로 나중에 실행된다.
9. 이벤트 루프의 한계
이벤트 루프에도 한계가 있다:
function infiniteMicrotasks() {
Promise.resolve()
.then(() => {
console.log("마이크로태스크");
infiniteMicrotasks();
});
}
infiniteMicrotasks();
setTimeout(() => console.log("타이머"), 0);
// "마이크로태스크" 무한 출력
// "타이머"는 절대 실행되지 않음
마이크로태스크가 끝없이 쌓이면 태스크 큐로 넘어갈 기회가 없다. 이벤트 루프가 멈춘다.
10. 성능과 흐름 제어
이벤트 루프가 코드에 미치는 영향을 보자:
- 성능: 태스크와 마이크로태스크를 잘 분배하면 부하를 줄일 수 있다.
- 흐름: 실행 순서를 예측하고 제어할 수 있다.
태스크와 마이크로태스크의 우선순위를 이해하는 게 핵심이다.
마무리
이벤트 루프와 태스크 스케줄링은 자바스크립트의 비동기 동작을 이해하는 열쇠다. 호출 스택, 태스크 큐, 마이크로태스크 큐가 어떻게 조화를 이루는지 알면 코드 흐름을 더 잘 다룰 수 있다.
'코딩 공부 > 자바스크립트' 카테고리의 다른 글
67. 자바스크립트 async/await 최적화 (Optimizing Async/Await) (1) | 2025.03.22 |
---|---|
66. 자바스크립트 프로미스 병렬 처리 심화 (Advanced Promise Parallelism) (1) | 2025.03.22 |
64. 자바스크립트 비동기 패턴 심화 (Advanced Async Patterns) (1) | 2025.03.21 |
63. 자바스크립트 비동기 제너레이터 (Async Generators) (4) | 2025.03.21 |
62. 자바스크립트 비동기 이터레이터 (Async Iterators) (1) | 2025.03.20 |