Node.js 이벤트 루프(Event-Loop)

terry yoon·2021년 9월 13일
2
post-thumbnail

자바스크립트의 범위

우리가 흔히 말하는 자바스크립트는 무엇을 가르키는 것일까?
이를 이해하기 위해서 우선 ECMA Script에 대한 이해가 필요하다.

ECMA Script는 95년 브렌던 아이크에 의해서 초창기 버전의 자바스크립트가 마이크로소프트와의 크로스 브라우징 이슈로 인해 비영리 기구인 ECMA 인터내셔널에 의해서 정해진 표준이다.

즉, ECMA Script는 모든 자바스크립트 지켜야하는 표준이자 규약이며 이를 자바스크립트의 코어(Core)라고 한다. 이 표준을 기준으로 각 브라우저 제조사에 따라서 웹 상에서 동작하는 자바스크립트는 ECMA Script를 포함해, 클라이언트 Web API를 포함하는 개념이다.

이후, 브라우저 이외의 환경에서도 자바스크립트가 동작할 수 있도록 만들기 위해 제작된 것이 Node.js이며, Node.js에서 말하는 자바스크립트는 ECMA Script를 포함해 Node.js의 빌트인 API를 말한다.

Node.js 는 단일 쓰레드(Single Thread) 이벤트 루프(Event-loop)를 기반으로 동작한다. 우리가 알아 볼 것은 바로 노드 환경에서의 자바스크립트의 이벤트 루프에 대해 알아볼 것이다.

싱글 쓰레드 vs 멀티 쓰레드

프로세스와 쓰레드

쓰레드 개념 전에 프로세스란 개념을 알아야 한다.
위키피디아에 따르면 프로세스는 다음과 같이 정의되어 있다.

프로세스(process)는 컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램을 말한다.

조금 쉬운 말로 다음과 같이 정의하기도 한다.

프로세스: 운영체제로부터 자원을 할당받은 작업의 단위.
쓰레드 : 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위.

즉 프로세스는 운영체제로 부터 CPU 자원을 할당받아 독립적인 객체를 의미한다. 따라서 각각의 프로세스는 독립적이므로 자원을 공유하지 않는다.

반면 쓰레드는 프로세스 안에서 프로세스가 할당받은 자원을 이용하여 실제 작업을 실행하는 실행 흐름의 단위이다.

쉽게 말하면 작업의 독립적인 단위가 프로세스이고, 프로세스 안에 여러 쓰레드가 프로세스의 자원을 공유하여 작업을 실행하게 된다.

싱글 스레드와 멀티 스레드

단어에서도 차이가 나듯이, 싱글 스레드는 프로세스의 실행 흐름을 1개로 멀티 쓰레드는 실행의 흐름을 여러 개로 나누어 작업을 실행하는 것이다.

즉 싱글 쓰레드는 한 번에 하나의 작업만 실행(Sequential)한다면, 멀티 쓰레드는 동시에 여러 작업(Concurrency)을 처리할 수 있다.

위에 말에 따르면 Node 환경의 자바스크립트는 싱글 스레드 기반으로 동작한다는 것은 그럼, 자바스크립트가 한 번에 한 번의 일만 수행할 수 있다는 말과 같은가?

하지만 자바스크립트는 우리의 생각과 달리 동시에 많은 일을 처리하고 있다. 과연 어떻게 싱글 스레드로 동시성(Concurrency)을 구현할 수 있을까?

이벤트 루프와 비동기 방식

ECMA Script는 이벤트 루프가 없다

실제로 ECMAScript 스펙에 이벤트 루프에 대한 내용이 없기 때문일 것이다. 좀더 구체적으로 표현하면 'ECMAScript 에는 동시성이나 비동기와 관련된 언급이 없다'고 할 수 있겠다.

실제로 V8과 같은 자바스크립트 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청이 들어올 때마다 해당 요청을 순차적으로 호출 스택에 담아 처리할 뿐이다. (ECMA Script가 아님을 주의하자!)

그렇다면 비동기 요청은 어떻게 이루어지며, 동시성에 대한 처리는 누가 하는 걸까? 바로 이 자바스크립트 엔진을 구동하는 환경, 즉 브라우저나 Node.js(libuv 라이브러리)가 담당한다.

즉, 단일 쓰레드 기반의 언어란 자바스크립트 엔진이 단일 호출 스택을 사용한다는 관점에서 사실이다.

왜냐하면 자바스크립트에서 함수가 실행되면, 함수가 실행되어 완료되기 전까지 다른 함수가 실행을 끼어들 수 없기 때문이다.(Run to Completion) 이런 점이 자바스크립트 언어가 단일 쓰레드 방식으로 동작한다는 말과 동일한 것이다.

이벤트 루프는 이런 단일 쓰레드로 동작하는 자바스크립트와 실제 자바스크립트가 구동되는 멀티 스레드 환경을 상호 연동하기 위한 장치인 것이다.

이벤트 루프

이벤트 루프 동작 예제

브라우저 환경에서 자바스크립트 엔진은 함수가 호출 시 실행 컨텍스트를 생성하고 이 실행 컨텍스트가 콜 스택을 구성하게 된다.

다음 코드를 한 번 보자

request('http://www.google.com', function(error, response, body) {
  console.log(body);
});

console.log('Done!');

위 코드를 순서대로 실행하면 다음과 같다.

알아두기 : 우선 자바스크립트 엔진에 의해서 코드가 해석되면서 call stack에 해당 코드의 실행 컨텍스트를 push하게 됩니다. 이후 call stack에 순서대로 함수를 실행하면 해당 스택을 제거(pop)하게 됩니다.

1) request 함수가 호출되어 call stack에 push 되고, 실행되어 call stack에서 pop

2) request 함수가 호출되면서 Request Web API가 실행됩니다. 해당 API 처리가 완료되면 callBack 함수로 전달한 익명 함수가 Callback queue에 올라가게 됩니다.

3) console.log('Done!')이 call stack 에 쌓이게 됩니다.

4) call stack에 아무 것도 없고, callback queue에 task가 존재한다면 Event-loop은 해당 task를 callStack에 올려서 해당 callback 함수를 실행합니다.

5) call stack과 callback queue에 아무 것도 없다면 프로그램을 종료합니다.

이벤트 루프의 동작 원리

위의 코드 예시를 보듯이 이벤트 루프의 동작 원리는 간단하다.

call stack과 callback queue를 지속적으로 감시(busy-wating)하면서 call stack에 task가 빈 경우 callback queue에 작업이 있는지 확인하고 이를 call stack으로 옮겨오는 역할을 한다.

while(queue.waitForMessage()){
  queue.processNextMessage();
}

위의 코드는 이벤트 루프를 설명한 mdn에서 이벤트루프를 설명한 간단한 코드이다. 위 코드를 보면 이벤트 루프는 테스크 큐(Task-queue)에 테스크가 들어오기 전까지 계속 대기하다, 테스크가 발생하면 이를 처리한다.

event-loop 이 call stack이 비어있는지를 확인하는 방식에 대해 더 조사할 필요가 있다. 즉, busy-waiting 을 통해 call stack이 비어있다면 이를 인지하는 것인지 혹은 call stack이 빈 경우, event를 발생시켜 event-loop을 interrupt하는 방식인지 확실치 않다.

추가 조사 필요 : 매크로 테스크와 마이크로 테스크

참고 자료

NHN Cloud 블로그
프로세스와 쓰레드
The JavaScript Event Loop: Explained
참고

profile
배운 것을 기록하는 FrontEnd Junior 입니다

0개의 댓글