Programming/JavaScript & TypeScript

[Node.js] 싱글 스레드(Single-thread)와 이벤트 루프(Event loop)

Bonita SY 2020. 9. 25. 22:58
728x90
반응형

Node.js는 Chrome의 V8 JavaScript 엔진 에 구축 된 JavaScript 런타임

 

비동기 이벤트 기반 JavaScript 런타임 인 ​​Node.js는 확장 가능한 네트워크 애플리케이션을 빌드하도록 설계됨.

Event loop

- JavaScript가 싱글 스레드라는 사실에도 불구하고, 가능할때마다 작업을 시스템 커널로 offload하여 non-blocking IO 작업을 수행할 수 있도록 함

- 대부분의 최신 커널은 다중 스레드이므로 백그라운드에서 실행되는 여러 작업을 처리 가능

- 이러한 작업 중 하나가 완료되면, 커널은 Node.js에 적절한 콜백을 poll queue에 추가하여 실행되도록 함

 

- Node.js가 시작되면, 이벤트 루프를 초기화하고 제공된 input script를 처리하여 async API 호출, 스케쥴 타이머, process.nextTick() 호출을 수행할 수 있음

- 그런 다음에 이벤트 루프 처리를 시작

 

- 각 단계에는 실행할 callback의 FIFO 대기열이 존재

- 일반적으로 이벤트 루프가 주어진 단계에 진입하면 해당 단계에 특정한 작업을 수행한 다음 대기열이 소진되거나 최대 콜백 수에 도달할 때까지 해당 단계의 대기열에서 콜백을 실행함

- 대기열이 소진되었거나, 콜백 limit에 도달하면, 이벤트 루프가 다음 단계로 이동하는 방식

- 폴링 이벤트가 처리되는 동안 폴 이벤트가 큐에 추가될 수 있음

각 단계 설명

timers

- setTimeout() 및 setInterval()에 의해 예약된 콜백을 실행

- 주어진 콜백이 실행될 수 있는 임계값을 지정

- timer callback은 지정된 시간(임계값)이 지나면 스케쥴링할 수 있을 때 최대한 빨리 실행 (하지만 운영 체제 스케쥴이나 다른 콜백 실행으로 인해 지연될 수 있음)

- 기술적으로는 poll 단계는 timers가 실행되는 시기를 제어

pending callbacks

- 다음 loop 반복으로 지연된 I/O 콜백을 실행

- TCP 오류와 같은 시스템 동작에 대한 콜백을 실행

ex) TCP 소켓이 연결을 시도할 때 'ECONNREFUSED'를 수신하면, 일부 *nix 시스템은 오류보고를 기다림. 그럼 pending callback에서 실행될 수 있도록 queue에 추가됨

idle, prepare

(내부적으로 사용)

poll

- 새로운 I/O 이벤트를 검색

- I/O 관련된 콜백(ex. close callback의 예외, timer에 의한 스케쥴, setImmediate())을 실행

- 노드는 적절한 경우 여기에서 차단됨

 

주요 기능

1. I/O를 block과 polling 해야하는 기간을 계산

2. poll queue의 event 처리

 

이벤트 루프가 poll 단계에 들어가고, 예약된 타이머가 없으면 다음 2가지 중 1개가 발생됨

- poll queue가 비어있지 않은 경우 : 이벤트 루프는 큐가 소진되거나 system-dependent 하드 한계에 도달할 때까지 동기식으로 실행하는 콜백 큐를 반복

- poll queue가 비어있는 경우

-- script가 setImmediate()에 의해 스케쥴된 경우 : 이벤트 루프는 풀 단계를 종료하고, 예약된 스크립트를 실행하기 위해 check단계를 계속 수행

-- script가 setImmediate()에 의해 예약되지 않은 경우 : 이벤트 루프는 콜백이 대기열에 추가될 때까지 기다린 다음 죽시 실행

 

- poll queue가 지면 이벤트 루프는 시간 임계값에 도달한 타이머를 확인

- 하나 이상의 타이머가 준비된 경우, 이벤트 루프틑 timers 단계로 다시 래핑되어 해당 타이머의 콜백을 실행

check

- setImmediate() 콜백이 여기서 호출

- 이 단계에서 poll 단계가 완료된 후 즉시 콜백을 실행 가능

- poll 단계가 유휴 상태가 되고, 스크립트가 setImmediate()로 대기열에 추가된 경우, 이벤트 루프는 대기하지 않고 check 단계를 계속할 수 있음

 

-  setImmediate()는 실제로 이벤트 루프의 별도 단계에서 실행되는 특수 타이머

- poll 단계가 완료된 후 실행할 콜백을 예약하는  libuv API를 사용

 

- 일반적으로 이벤트 루프는 들어오는 connection, request 등을 기다리는 poll 단계에 도달

- 그러나 callback이 setImmediate()로 예약되고, poll 단계가 유휴 상태가 되면 poll 이벤트를 기다리지 않고 종료되며, check 단계를 계속 수행

close callbacks

- 일부 close callback (ex. socket.on('close')...)

- 소켓이나 handler가 갑자기 닫히면, 이 단계에서 'close' 이벤트가 발생함 ex) socket.destroy()

- 그렇지 않으면 process.nextTick()를 통해 방출됨

 

이벤트 루프가 실행될 때마다, Node.js는 비동기 I/O나 timer가 대기중인지 확인하고 없는 경우 완전히 종료

setImmediate() vs setTimeout()

- 두 함수는 비슷하지만 호출 시기에 따라 다른 방식으로 작동

- 타이머가 실행되는 순서는 호출되는 내용에 따라 다르고, 두  함수 모두 기본 모듈 내에서 호출되는 경우 타이밍은 프로세스의 성능에 의해 결정됨

setImmediate()

- poll 단계가 완료되면 script를 실행하도록 설계됨

setTimeout()

- ms 단위의 최소 임계값이 경과한 후에 script가 실행되도록 예약

 

setTimeout() 보다 setImmediate()를 사용하는 가장 큰 이유는 I/O 주기 내에 스케쥴링된 경우 setImmediate()가 항상 타이머 수에 관계없이 먼저 실행됨

process.nextTick()

- 비동기 API 중 하나

- nextTickQueue는 이벤트 루프의 현재 작업 중인 단계에 관계없이 현재 작업이 완료된 후에 처리

※ 작업 : c/c++ 처리기에서 전환하고 실행해야하는 JavaScript를 처리하는 것

- 각 단계에서 process.nextTick()을 호출할 때 마다, process.nextTick()에 전달된 모든 콜백은 이벤트 루프가 계속되기 전에 해결됨

-- 이벤트 루프가 poll 단계에 도달하는 것을 방지하는 재귀적 process.nextTick() 호출을 만들어 I/O를 "starve"시킬 수 있기 때문에

RangeError: Maximum call stack size exceeded from v8

process.nextTick() vs setImmediate()

- process.nextTick()은 같은 단계에서 즉시 실행됨

- setImmediate()는 이벤트 루프의 다음 반복 또는 'tick'에서 발생

- 즉_ process.nextTick()은 setImmediate() 보다 즉시 실행됨

- 개발자들은 모든 경우에 setImmediate()를 사용하는 것을 권고

process.nextTick()은 언제 써?

- 사용자가 오류를 처리하고, 불필요한 리소스를 정리하거나, 이벤트 루프가 계속되기 전에 request를 다시 시도해야하는 경우

- call stack이 풀린 후 이벤트 루프가 계속되기 전에 콜백이 실행되도록 허용해야 하는 경우

 

 

출처

https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

728x90
반응형