본문 바로가기
BackEnd

Node.js 의 이벤트 루프

by 마운틴케이 2024. 3. 5.

Node.js 를 공부하다 이벤트 루프라는 개념이 어려워 정리해두었다.


이벤트 루프란?

Node.js 는 하나의 스레드(싱글 스레드)에서 작업을 처리. 

이벤트 루프란 시스템 커널에서 가능한 작업이 있다면 그 작업을 커널에 이관하여 JavaScript 가 싱글 스레드임에도 논블로킹(비동기) I/O 작업을 수행할 수 있게 해주는 기능.

 

이벤트 루프의 단계

  • 아래 그림의 화살표 방향으로 루프가 실행. (timers → pending callbacks …. → close callbacks → timers …)
  • 각 단계에는 각각 실행할 콜백 함수를 담기 위한 큐가 존재
  • 이벤트 루프가 특정 단계에 진입하면 해당 단계에서 필요한 작업을 수행 후, 해당 단계의 큐에서 콜백을 실행. 실행하여 큐가 모두 비거나 콜백 제한 수에 도달하게 된다면 이벤트 루프가 다음 단계로 이동.
  • js 코드는 유휴, 준비(idle, prepare) 단계를 제외한 어느 단계에서나 실행될 수 있음
   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

 

* 틱(tick) ? 이벤트 루프에서 다음 단계로의 이동을 말함.

 

타이머 단계(timers)

  • setTimeout(), setInterval() 에 의해 만들어진 타이머를 큐에 넣고 실행.
  • 실행할 시간이 지난 타이머들이 대상. 대상인 콜백이 무한정 실행되는 것은 아니고 시스템의 실행 한도에 도달하면 다음 단계로 넘어감.
  • 타이머들은 최소 힙으로 관리되며, 힙을 구성할 때 기준으로 실행할 시각이 가장 적게 남은(가장 빠르게 실행할 수 있는) 타이머가 힙의 루트가 됨

대기 콜백 단계(pending callbacks)

  • 이 단계의 큐에는 현재 돌고 있는 이벤트 루프 이전의 작업에서 큐에 들어온 콜백이 담김.
  • TCP 에러 등 일부 시스템 작업에 대한 콜백을 실행.
  • 예) TCP 소켓이 연결을 시도할 때 ECONNREFUSED(연결이 되지 않았을 때) 오류를 수신할 경우, 이 오류를 보고하기 위해 일부 *nix 시스템(Unix, Linux 등 유사 운영 체제)은 대기할 수 있음.
  • 대기 단계에 들어왔을 때 이전 작업들의 콜백이 큐에서 대기 중인지 검사 후 실행 대기 중이라면 시스템 실행 한도에 도달할 때까지 꺼내어 실행.

유후, 준비 단계(idle, prepare)

  • Node.js 의 내부 동작을 위해서만 사용됨
  • 유휴 단계는 틱마다 실행되며, 준비 단계는 매 폴링(poll) 직전에 실행.

폴 단계(poll)

  • 새로운 I/O 이벤트를 찾고, I/O 관련 콜백을 실행.
  • 이 단계에서는 watch_queue 라는 큐를 갖고 있다.
  • 이벤트 루프가 폴 단계에 진입한 상태에서 예약된 타이머가 없을 경우 다음 중 하나 발생
    • 폴 큐가 비어있지 않을 경우 큐가 모두 소진 되거나 시스템 제한에 도달할 때까지 동기적으로 폴 큐의 모든 콜백을 실행.
    • 폴 큐가 비어있을 경우
      • 체크 단계의 큐, 대기 단계의 큐, 종료 단계의 큐에 남은 작업이 있는지 검사
      • setImmediate() 콜백이 있는 경우, 폴 단계를 종료하고 체크 단계로 이동
      • setImmediate() 없을 경우 콜백이 큐에 추가될 때까지 대기 후 바로 실행
  • 폴 큐가 비어있으면 이벤트 루프는 타이머 단계에서 처리할 타이머가 있는지 확인하고 존재할 경우 타이머 단계로 돌아감

체크 단계(check)

  • setImmediate() 의 콜백만을 위한 단계로, 큐가 비거나 시스템 실행 한도에 도달할 때까지 콜백 수행
  • 폴 단계에서 큐가 비어있고 setImmediate() 로 스케줄된 콜백이 있을 경우, 폴 이벤트를 기다리지 않고 바로 체크단계 진행

종료 콜백 단계(close callbacks)

  • 소켓이나 핸들러가 갑자기 닫힐 경우, 혹은 process.nextTick() 을 통해 해당 이벤트 발생
  • 이벤트 루프는 종료 콜백 단계를 마치고 난 후 다음 루프에서 처리할 작업이 남아있는 지 검사. 작업이 남아있을 경우 타이머 단계부터 다시 루프를 돌고, 아닐 경우 루프 종료.

<참고>

NestJS 로 배우는 백엔드 프로그래밍 - 한용재 저

node.JS 공식 문서

 

 

댓글 피드백은 언제나 환영합니다!