아래는 Node.js 이벤트 루프 내에서 nextTickQueue와 microTaskQueue가 어떻게 작동하며, libuv와 어떻게 상호작용하는지에 대해 C++ 의사코드(pseudocode)와 함께 자세히 설명한 내용입니다.
process.nextTick()
으로 예약된 콜백들은 일반적인 마이크로태스크보다 더 높은 우선순위를 갖습니다.Promise
의 .then()
, queueMicrotask()
등에 의해 예약된 콜백들이 이 큐에 들어갑니다.Node.js의 이벤트 루프는 libuv를 기반으로 여러 **단계(phase)**로 구성되며, 각 단계마다 특정 작업들을 처리합니다. 각 단계가 끝날 때마다 Node.js는 우선 **nextTickQueue
**를, 그 다음 **microTaskQueue
**를 처리합니다.
while (true) {
// 1. Timers 단계: setTimeout, setInterval 콜백 실행
processTimers();
processNextTickQueue();
processMicroTaskQueue();
// 2. Pending Callbacks 단계: 시스템 I/O 관련 콜백 실행
processPendingCallbacks();
processNextTickQueue();
processMicroTaskQueue();
// 3. Poll 단계: 실제 I/O 이벤트 대기 및 처리 (libuv가 이 단계 담당)
int events = libuv_poll();
for (each event in events) {
handleEvent(event); // 예: 네트워크, 파일 시스템 I/O 처리
}
processNextTickQueue();
processMicroTaskQueue();
// 4. Check 단계: setImmediate 콜백 실행
processImmediateCallbacks();
processNextTickQueue();
processMicroTaskQueue();
// 5. Close Callbacks 단계: 소켓 종료 등 클린업 작업
processCloseCallbacks();
processNextTickQueue();
processMicroTaskQueue();
}
아래 코드는 Node.js의 이벤트 루프와 libuv, 그리고 nextTick와 microTask 큐의 처리를 단순화하여 보여줍니다.
#include <iostream>
#include <queue>
#include <functional>
#include <thread>
#include <chrono>
// 간단한 형태의 nextTickQueue와 microTaskQueue
std::queue<std::function<void()>> nextTickQueue;
std::queue<std::function<void()>> microTaskQueue;
// 모의 함수: nextTick에 작업 추가
void processNextTick(std::function<void()> fn) {
nextTickQueue.push(fn);
}
// 모의 함수: microTask에 작업 추가
void processMicroTask(std::function<void()> fn) {
microTaskQueue.push(fn);
}
// 각 큐를 처리하는 함수
void runNextTickQueue() {
while (!nextTickQueue.empty()) {
auto fn = nextTickQueue.front();
nextTickQueue.pop();
fn();
}
}
void runMicroTaskQueue() {
while (!microTaskQueue.empty()) {
auto fn = microTaskQueue.front();
microTaskQueue.pop();
fn();
}
}
// 모의 libuv poll 단계 (여기서는 단순히 타이머처럼 동작)
void libuvPollPhase() {
// 여기서 실제 I/O 이벤트가 발생했다고 가정
std::cout << "[Poll] I/O 이벤트 감지" << std::endl;
// 예: I/O 완료 콜백을 큐에 추가
processMicroTask([](){
std::cout << "[MicroTask] I/O 완료 콜백 실행" << std::endl;
});
}
int main() {
// 초기 작업: process.nextTick() 사용 예
processNextTick([](){
std::cout << "[NextTick] 초기 작업 실행" << std::endl;
});
// 모의 이벤트 루프
for (int tick = 0; tick < 3; ++tick) {
std::cout << "\\n=== 이벤트 루프 단계 " << tick << " 시작 ===" << std::endl;
// 타이머 단계 (예: setTimeout)
std::cout << "[Timers] 타이머 콜백 실행" << std::endl;
processMicroTask([](){
std::cout << "[MicroTask] 타이머 콜백 실행" << std::endl;
});
runNextTickQueue();
runMicroTaskQueue();
// I/O 이벤트 단계 (poll 단계)
libuvPollPhase();
runNextTickQueue();
runMicroTaskQueue();
// Check 단계 (예: setImmediate)
std::cout << "[Check] setImmediate 콜백 실행" << std::endl;
processNextTick([](){
std::cout << "[NextTick] setImmediate 후 실행" << std::endl;
});
runNextTickQueue();
runMicroTaskQueue();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return 0;
}
nextTickQueue
와 microTaskQueue
는 각각 process.nextTick()
와 마이크로태스크(Promise 등)로 예약된 작업을 저장하는 큐입니다.runNextTickQueue()
와 runMicroTaskQueue()
함수는 해당 큐의 작업들을 모두 실행합니다.libuvPollPhase()
함수는 I/O 이벤트가 발생했다고 가정하고, I/O 완료 콜백을 microTask 큐에 추가하는 모의 함수입니다.