Node.js의 Event Loop

ESH'S VELOG·2023년 9월 24일
1

이전 글에서는 Javascript의 Event Loop에 대해서 알아보았다.

이번에는 Node.js의 Event Loop를 알아보려고 한다.

우선, Node.js란 무엇일까?

Javascript를 브라우저 밖에서도 실행할 수 있도록 하는 Javascript의 런타임이다.
= 즉, Javasciprt는 V8엔진과 브라우저의 도움을 받아 실행할 수 있었는데 브라우저의 도움 없이도 런타임 할 수 있게 만들어주는 환경인 것이다.

Node.js 특징을 또한 보자.

1) Single Thread 기반
2) Event Driven 아키텍처 (이벤트 기반)
: 미리 지정해둔 작업을 수행하는 방식
3) Non-Blocking I/O
싱글쓰레드는 Javascript기반이기 때문에 그렇다 치는데, 2번과 3번은 무슨말이지?

  • I/O의 뜻?

    I/O란 데이터의 입력(Input)과 출력(Output)을 함계 일컫는 말이다. 어떤 디바이스를 통해 입력과 출력이 이뤄지는 작업을 모두 I/O라고 한다.

  • Blocking I/O vs Non-Blocking I/O ?

    • Blocking I/O : I/O작업이 진행되는 동안 유저 프로세스가 자신의 작업을 중단한 채, I/O가 끝날때까지 대기하는 방식, I/O가 호출되면 제어권을 가져가서 어플리케이션이 멈춤
    • Non-Bloking I/O : A함수가 I/O작업을 호출했을 때, A함수의 작업을 중단하지 않고 I/O호출에 대해 즉시 리턴하고 A함수가 이어서 다른 일을 수행할 수 있도록 하는 방식, I/O가 완료 될 때까지 대기하지 않고, 제어권을 어플리케이션이 가짐.

다음은 Node.js가 어떻게 동작을 할 수 있는지 알아보자.
1) V8 Engine기반으로 동적 코드와 call stack, heap memory, garbage collector등 기본적으로 자바스크립트와 동일하게 실행할 수 있는 환경을 만들어 준다.
2) Libuv 라이브러리
이전 글에서는 javascript는 WebAPI에서 비동기함수를 처리할 수 있었는데 Node.js환경에서는 Libuv 라이브러리가 이를 담당하게 된다.
Libuv의 특징(비동기화 처리)

  • C언어로 작성된 런타임
  • 커널을 가지고 있다.
  • thread pool을 가지고 있다.
    : 기본적으로 4개의 스레드를 가지고 있고 uv_threadpool이라는 환경 변수를 설정해 최대 128개까지 스레드 개수를 늘릴 수도 있다.

어?? 스레드가 여러 개라고?? 그렇다면 Node.js는 왜 멀티스레드가 아니고 싱글스레드라는 걸까?

출처: https://blog.usejournal.com/nodejs-architecture-concurrency-model-f71da5f53d1d (NodeJS Architecture)

위의 그림으로 설명하자면 다음과 같다.
비동기 요청이 들어오면 아래의 순서로 실행된다.
1. 요청이 들어오면 libuv가 해당 요청이 커널이 지원하는 요청인지 확인한다.(blocking I/O인지 non-blocking I/O인지)
2. 커널이 지원하는 요청이라면 커널에서 응답을 해준다.
3. 커널이 지원하지 않는 요청라면 요청이 해당되는 Thread Pool에 선택되어 작업을 위임한다.
4. Event Loop는 순차적으로 Thread Pool을 돌면서 call stack이 비어있는지 체크하고 Event Queue에 실행 대기중인 callback이 있다면 callback들을 call stack으로 이동시켜 Main Thread에 의해 실행될 수 있게 만들어준다.

3번에서 Thread Pool에 선택된다고 했는데 이 Thread Pool은 총 6가지의 phase를 가지고 있다.
이를 Event Loop Phases라고 한다.

출처: https://www.voidcanvas.com/nodejs-event-loop/

이는 이전 javascript에서 설명했던 task queue, microtask queue, animation frames와 비슷한데 더 복잡하다.

1. Timer

setTimeout, setInterval과 같은 타이머에 관련된 callback을 처리

2. Pending Callbacks

처리하지 못하고 넘어간 작업들을 쌓아놓고 실행하는 페이즈, 에러 핸들러 콜백 등

3. Idle, Prepare

Node.js의 내부적인 관리를 위한 페이즈로 자바스크립트를 실행하지 않으며 코드의 직접적인 실행에 영향을 미치지 않는다.

4. Poll

대기중인 callback을 call stack으로 가장 많이 올려보내는 단계로 I/O에 대한 거의 모든 콜백들이 담긴다.
ex)

  • DB에 쿼리를 보낸 후 결과가 왔을 때 실행되는 콜백
  • HTTP 요청을 보낸 후 응답이 왔을 때 실행되는 콜백
  • 파일을 비동기로 읽고 다 읽었을 때 실행되는 콜백

5. Check

setImmediate() 만을 위한 단계

6. Close Callbacks

socket.on('close', () => {}) calose 이벤트 타입의 핸들러를 처리하는 페이즈로, uv_close()를 부르면서 종료된 핸들러의 콜백들을 처리하는 페이즈다.

javascript에서 하나의 callback queue로만 관리된 반면에 모든 phase들은 자신만의 event queue를 가지고 있으며 event loop는 각 phase를 순회하면서 각자의 phase event queue에서 실행할 callback을 call stack으로 이동시킨다.

참고자료
https://medium.com/zigbang/nodejs-event-loop%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-16e9290f2b30
https://www.korecmblog.com/blog/node-js-event-loop

공부하면서 node.js의 event loop의 큰 단락들을 먼저 보기위해 많은 부분을 생략하였다.
의문점은 이벤트루프의 phase가 6가지나 되는데 check과 close callbacks phase가 쓸모가 떨어지는데 굳이 많을 필요가 있을까 하는 의문이다..

profile
Backend Developer - Typescript, Javascript 를 공부합니다.

0개의 댓글