Node JS 그리고 Event Loop

김동욱·2022년 12월 1일
1

Nest.JS

목록 보기
2/2

Node JS는 단일 쓰레드다. 그런데 비동기가 지원이 된다고?


멀티 쓰레드와 단일 쓰레드


  • 멀티 쓰레드

    여러 작업을 처리해야할 때, 여러 쓰레드를 만들고 작업을 생선된 여러 개의 쓰레드에 할당하는 것을 멀티 쓰레드 방식이라고 한다. 이는 여러 작업을 동시에 처리함을 가능하게 한다.

    당연히 작업 속도가 빨라진다.

    단, 메모리 등 컴퓨터 자원 관리에 신경을 많이 써야하고 잘못된 코드 작성으로 동기화 오류가 날 여지가 크다. 다들 알다시피 비동기-동기 관련 오류가 발생했을 때 이를 디버깅하는 것은 매우 성가신 일이다.


  • 단일 쓰레드

    말 그대로 작업들을 단일한 쓰레드에서 처리한다. 즉, 서버 자원에 큰 부하가 가지 않는다. 또한 다중 쓰레드에 대한 고찰을 하지 않고 코드를 짤 수 있는 것은 매우 편리한 일이다.

    Node.js는 바로 이 단일 쓰레드 방식을 사용한다. 그러면 비동기가 불가능한 것인가? 사실 백그라운드 상에 쓰레드 풀이 구성되어 있다. Node.js의 libuv5가 쓰레드 풀을 구성하고 관리한다. 개발자가 직접 쓰레드를 관리하지 않기 때문에 간단하게 단일 쓰레드 상에서 동작하는 것으로 이해하면 되는 코드를 작성하면 된다.

Event Loop, 비동기를 가능케 하다.


이벤트 루프

이벤트 루프에는 6개의 단계(Phase)가 있다.
각 단계는 단계마다 처리해야 하는 콜백 함수를 담기 위한 큐를 가지고 있다.
자바스크립트 코드는 idle & prepare 단계를 제외한 어느 단계에서나 실행될 수 있다.

프로그램이 실행된다. 그러면 Node.js는 이벤트 루프를 구성한 뒤, 개발자가 작성한 코드를 읽고 여러 콜백을 만들어낸다. 그리고 이들을 각 단계에 존재하는 큐에 넣고 메인 모듈의 실행을 완료한 다음 이벤트 루프를 실행한다. 이벤트 루프 상의 큐에서 더 이상 수행할 작업이 없다면 Node.js는 루프를 빠져나가고 프로세스를 종료한다.

Timers 단계

이벤트 루프는 Timer 단계에서 시작한다. 해당 단계의 큐에는 setTimeout이나 setInterval과 같은 함수를 통해 만들어진 타이머들을 큐에 넣고 실행한다. 이때 실행 한도(Hard Limit)이 존재하는데 이 실행 한도에 도달하게 되면 해당 단계에서 벗어난다.

Pending Callbacks

이 단계의 큐에 들어있는 콜백들은 현재 돌고 있는 루프 이전의 작업에서 큐에 들어온 콜백이다.
Timer 단계를 거쳐 pending 콜백 단계에 들어오면 이전 작업들의 콜백이 pending_queue에서 대기중인지를 검사한다. 만약 실행 대기 중이라면 시스템 실행 한도에 도달할 때까지 꺼내어 실행한다.

Idle, Prepare 단계

Idle 단계는 매 단계가 이동할 때마다 실행됩니다. Prepare 단계는 매 폴링마다 그 전에 실행된다. (이들은 Node.js의 내부 동작을 위한 것)

Poll 단계

Poll 단계에서는 I/O 입출력 이벤트를 가져와서 관련 콜백을 수행한다.
이 단계의 큐인 watch_queue가 비어 있지 않다면 큐가 비거나 시스템 실행 한도에 다다를 때까지 동기적으로 모든 콜백을 실행한다.
이 큐가 비어있다면 Node.js는 곧바로 다음 단계로 이동하지 않고 Check 단계의 큐, Pending 콜백 단계의 큐, Close 콜백 단계의 큐에 남은 작업이 있는지 검사한 다음 작업 있다면 다음 단계로 이동한다. 만약 큐가 모두 비어서 해야할 작업이 없다면 잠시 대기를 하게 된다.
이때 대기시간은 타이머 최소 힙의 첫번째 타이머를 꺼내어 지금 실행할 수 있는 상태라면 그 시간만큼 대기한 후 다음 단계로 이동한다. 이렇게 하는 이유는 바로 타이머 단계로 넘어간다고 해도 어차피 첫번째 타이머를 수행할 시간이 되지 않았기 때문에 이벤트 루프를 한 번 더 돌아야 하므로 Poll 단계에서 시간을 보내는 것이다.

Check 단계

Check 단계는 setImmediate의 콜백만을 위한 단계. 역시 큐가 비거나 시스템 실행 한도에 도달할 때 까지 콜백을 수행한다.

Close 콜백 단계

socket.on('close', () => {})과 같은 close나 destroy 이벤트 타입의 콜백이 여기서 처리된다. 이벤트 루프는 Close 콜백 단계를 마치고 나면 다음 루프에서 처리해야 하는 작업이 남아 있는지 검사한다. 있으면 계속 루프를 돌고 없으면 루프를 나간다.

nextTickQueue과 microTaskQueue

이 둘은 이밴트 루프의 일부분이 아니라 Node.JS에 포함된 기술이다. 즉, libuv에 포함된 큐는 아니다. 이 두 큐에 들어 있는 콜백은 단계를 넘어가는 과정에서 먼저 실행된다. nextTickQueue가 microTaskQueue보다 높은 우선순위를 가지고 있다.

profile
nestjs 백엔드 개발합니다.

0개의 댓글