컴퓨터 공학/JavaScript

Node.js 이벤트 루프 정리

혼새미로 2021. 4. 17. 17:38
반응형
 
 
[용어정의]
  • REPL (Read-Eval-Print Loop) : 단일 사용자의 입력을 취하고 이를 평가 (실행)하고 결과를 사용자에게 반환시키는 단순한 상호작용 컴퓨터 프로그래밍 환경을 말한다. (예: 주피터 노트북)
  • Polling : IT에서 클라이언트가 서버에게 주기적으로 요청을 보내는 방식을 말한다.
 
 
[개요]
  • 이벤트 루프가 각 단계에 진입하면 해당 단계에 한정된 작업을 수행하고 큐를 모두 소진하거나 콜백의 최대 개수를 실행하면 다음 단계로 넘어간다.
 
 
[이벤트 루프 단계 요약]
  • 타이머 (timers) : setTimeout()setInterval()로 스케줄링한 콜백을 실행한다.
  • 대기콜백 (pending callbacks) : 다음 루프 반복으로 연기된 I/O 콜백들을 실행한다.
  • 준비 (idle, prepare) : 내부용으로만 사용한다.
  • 폴 (poll) : 새로운 I/O 이벤트를 가져온다. I/O와 연관된 콜백 (닫기콜백, 타이머로 스케줄링된 콜백, setImmediate()를 제외한 거의 모든 콜백)을 실행한다.
  • 검사 (check) : setImmediate() 콜백은 여기서 호출된다.
  • 닫기콜백 (close callback) : 일부 닫기콜백들, 예를 들어, socket.on('close',...)가 여기서 실행된다.
 
 
 
[타이머 단계]
  •  
  • 타이머는 사람이 실행하기를 원하는 정확한 시간이 아니라, 제공된 콜백이 최소 기다려야하는 기준 시간을 정한다.
  • Note : 기술적으로는 폴 단계에서 타이머를 언제 실행할지 결정한다.
 
 
[대기콜백 단계]
  • 이 단계에서는 TCP 오류와 같은 시스템 작업의 콜백을 실행한다.
  • 예를 들면, TCP 소켓이 연결을 시도하다가 ECONNREFUSED를 받으면 일부 *nix 시스템은 오류를 보고하기를 기다리려고 한다. 이는 대기콜백 단계에서 실행되기 위해 큐에 추가될 것이다.
 
 
[폴 단계]
#주요 기능
  • I/O를 얼마나 오래 블로킹하고 폴링해야 하는지 계산한다.
  • 폴 큐에 있는 이벤트를 처리한다.
 
#폴 큐
  • 비어있지 않은 경우
    • 이벤트 루프가 콜백의 큐를 순회하면서 큐를 다 소진할 때 까지 동기적으로 콜백을 실행한다.
  • 비어있는 경우
    • setImmediate()로 스케줄링된 경우
      • 이벤트 루프는 폴 단계를 종료하고 스케줄링된 콜백함수를 실행하기 위해 검사 단계로 넘어간다.
    • setImmediate()로 스케줄링되지 않은 경우
      • 이벤트 루프는 콜백이 큐에 추가되기를 기다린 후 즉시 실행된다.
 
 
[검사 단계]
  • 폴 단계가 완료된 직후 사람이 콜백을 실행할 수 있게 한다.
  • setImmediate()는 이벤트 루프의 별도 단계에서 실행되는 특수한 타이머이다.
  • setImmediate()는 폴 단계가 완료된 후 콜백 실행을 스케줄링하는데 libuv API를 사용한다.
  • 폴 단계가 유휴상태가 되고 스크립트가 setImmediate()로 큐에 추가되었다면 이벤트 루프를 기다리지 않고 검사 단계를 실행한다.
 
 
[닫기콜백 단계]
  • 소켓이나 핸들이 갑자기 닫힌 경우 (예: socket.destroy()), 이 단계에서 'close' 이벤트가 발생한다.
  • process.nextTick()도 여기서 실행된다.
 
 
[setImmediate()와 setTimeout()]
  • setImmediate()는 현재 폴 단계가 완료되면 스크립트를 실행하도록 설계되었다.
  • setTimeout()은 최소 임계 값 (ms)이 지난 후 스크립트가 실행되도록 스케줄링한다.
  • 폴 단계에서 I/O를 처리하기 때문에, I/O를 호출한 직후에는 반드시 setImmeidate()가 먼저 실행된다.
 
// timeout_vs_immediate.jsconst fs = require('fs');
 
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
//immediate
//timeout
 
 
[process.nextTick()]
  • process.nextTick()은 이벤트 루프에 속하지 않기 때문에, 현재의 작업이 끝난 직후에 바로 실행된다.
  • process.nextTick()의 콜백함수 내에서 process.nextTick()을 재귀적으로 호출하면 I/O 처리가 영원히 이루어지지 않을 수 있다.
  • process.nextTick()의 경우 현재 스크립트가 완료된 직후에 호출되기 때문에 다음과 같은 상황에서 유용하게 사용될 수 있다.
 
let bar;
 
// 비동기 시그니처를 갖지만, 동기로 콜백을 호출합니다.
function someAsyncApiCall(callback) { callback(); }
 
// `someAsyncApiCall`이 완료되면 콜백을 호출한다.
someAsyncApiCall(() => {
// someAsyncApiCall는 완료되었지만, bar에는 어떤 값도 할당되지 않았다.
console.log('bar', bar); // undefined
});
 
bar = 1;
 
  • 위의 코드에서는 콜백함수지만 비동기 작업이 없기 때문에 콜백함수가 즉시 호출되고, bar가 undefined으로 출력된다.
  • 이때 만약 process.nextTick()을 사용하여 현재 작업이 완료된 후에 콜백함수가 호출되도록 한다면 bar = 1로 출력되었을 것이다.
 
let bar;
 
function someAsyncApiCall(callback) {
process.nextTick(callback);
}
 
someAsyncApiCall(() => {
console.log('bar', bar); // 1
});
 
bar = 1;
 
 
 
반응형