Node.js 이벤트 루프 — 큐 우선순위
Node.js 의 “싱글 스레드인데 어떻게 동시에 많은 걸 처리하나” 는 이벤트 루프로 설명됩니다. 핵심은 콜백을 담는 큐가 하나가 아니고, 우선순위가 있다는 점입니다.
Call Stack 비움 — 다음 콜백 진입 조건
지금 이 순간 CPU 가 실행 중인 함수들이 쌓이는 곳이 Call Stack 입니다. 함수가 호출되면 쌓이고 끝나면 빠집니다. 이 스택이 완전히 비어야 이벤트 루프가 큐에서 다음 콜백을 하나 올립니다.
여기서 “한 요청이 서버 전체를 멈추는” 상황이 나옵니다. 어떤 콜백이 무거운 CPU 연산을 하면 Call Stack 이 안 비워지고, 그동안 다른 콜백은 큐에서 계속 기다립니다. I/O 가 아니라 CPU 작업이 이벤트 루프를 막는 겁니다.
큐를 관리하는 건 OS가 아니라 libuv
완료된 콜백이 대기하는 큐는 OS 큐가 아닙니다. libuv 가 Node 프로세스 안에서 직접 관리하는 자료구조입니다. 실제 파일·네트워크 I/O 는 libuv 가 OS 에 맡기고, 끝나면 그 결과 콜백을 이 큐에 넣습니다.
큐 우선순위
콜백 종류마다 큐가 다르고, 이벤트 루프가 비우는 순서가 정해져 있습니다.
nextTick 큐 ← 가장 먼저
↓
마이크로태스크(Promise)
↓
timers (setTimeout / setInterval)
↓
I/O 콜백
↓
check (setImmediate)
process.nextTick 과 Promise 의 .then 은 매 단계 사이마다 먼저
비워집니다. 그래서 setTimeout(fn, 0) 을 걸어도, 대기 중인 Promise 콜백이
있으면 그게 먼저 돕니다. “0ms 니까 즉시” 가 아니라 “timers 단계에서, 그것도
마이크로태스크를 다 비운 뒤” 입니다.
setTimeout(() => console.log('timeout'), 0)
Promise.resolve().then(() => console.log('promise'))
// 출력: promise → timeout
CPU 작업 분리 — 이벤트 루프 밖으로
무거운 CPU 연산이 이벤트 루프를 막으니, 그건 worker_threads 로 빼서 별도
스레드에서 돌립니다. 이벤트 루프가 지연되는지 감지하려면 루프 지연(event
loop delay)을 재는 방법도 있습니다. 원칙은 하나입니다 — 이벤트 루프를
도는 스레드에서 오래 걸리는 동기 작업을 하지 않는다.
큐는 libuv 안에, 우선순위는 nextTick부터
Node 의 동시성은 “빠른 콜백을 쉬지 않고 처리” 하는 구조입니다. 큐가 여러 개고
우선순위(nextTick·Promise 우선)가 있다는 것, 그리고 그 큐가 OS 가 아니라
libuv 안에 있다는 것을 알면 setTimeout(0) 이 왜 즉시가 아닌지, 왜 CPU
작업 하나가 전체를 멈추는지가 설명됩니다.
댓글 0