Kawaii_Jordy

[ARCH] Event Loop Architecture 본문

취준/Node.js

[ARCH] Event Loop Architecture

Kawaii_Jordy 2021. 5. 7. 16:26

Overview

만약 당신이 Node.js 개발자라면 Node.js의 core가 이벤트 루프(Event Loop)라는 이야기 또는 Node.js가 싱글 스레드라는 이야기를 또는 "setTimeout", "setimmediate" 함수가 어떻게 처리되는지 등에 대해 이미 많이 알고 있을 것이다.

그리고 기본적으로 Node.js는 non-blocking I/O 모델과 asynchronous 프로그래밍 스타일을 사용하고 있다 라고 다양한 블로글나 게시물에 설명되고 있다. 하지만 이에 대해서 쉽게 오해를 불러일으킬 수 있다고 생각이 들어 이번기회에 헷갈리던 부분들을 확실하게 해결해 보려고 한다.

Question

 

1. Node.js에서 Thread는 왜 필요할까?

 

프로세스는 최상위 수준의 실행 컨테이너이다. 한 프로세스에서는 다른 프로세스 메모리에 있는 데이터를 직접 얻을 수 없으므로 두 프로세스가 서로 통신할 수 없다. 그래서 IPC(Inter-Process Communication, 프로세서 간 통신)을 통해서 이프로세스 간 데이터를 주고 받게 된다. 이때 시스템 소켓을 통해서 IPC가 작동하게 된다. (Unix는 기본적으로 소켓을 기반으로 작동 하고, 소켓은 가상 "interface"(read/write/pool/close/etc)가 있는 커널의 객체를 가리킨다.)

여기서 시스템 소켓은 TCP 소켓 처럼 데이터를 버퍼로 변환 다음 전송하는 식으로 작동한다. Node.js 의 경우 두 프로세스를 통신할 때 JavaScript를 사용하기 때문에 JSON.stringify를 여러 번 호출해야 하기 때문에 속도가 현저하게 느려질수 있지만 이는 Tread를 통해 해결할 수 있다.

 

2. Node.js는 어떤 언어로 구현 되어 있고, 어떤 Thread에 관여할까?

 

Node.js는 V8 엔진이 내장되어 있고, 코드는 V8 엔진의 이벤트 루프가 실행되는 메인 스레드에서 실행된다. 그리고 Node.js는 V8 이외에도 libuv (C++)를 통해 구현된 다양한 NODE API가 존재하고, 이 또한 이벤트 루프에 의해 관리된다. (이 때 사용되는 C++ 때문에 Node.js는 30% 정도 C++ 이루어져 있다고 한다)

 

Node.js에서 C++는 자바스크립트 코드 뒤에서 동작하며 스레드에 접근할 수 있다. 그리고 Synchronous 메소드를 호출되면 항상 메인 스레드에서 실행이 되지만 Asynchronous 메서드가 호출된다면 Event Loop가 해당 메서드를 NODE API들 중 하나에 라우팅할 수 있고, 메인 스레드 이외의 다른 스레드에서 처리될 수 있다.

 

3. Node.js에서 이벤트 루프(Event Loop)의 기능은 무엇일까?

 

 

Node.js의 이벤트 루프에서는 시스템 커널(Thread Pool)에 작업을 떠넘겨서 Node.js가 논 블로킹 I/O 작업을 수행하도록 해준다(개발자가 따로 코드를 구현하거나 실행시키지 않아도 알아서 해줌) 이는 JavaScript가 싱글 스레드임에도 불구하고, 백그라운드에서 다양한 작업을 실행할 수 있는 이유가 된다. Thread Pool에서 요청을 보낸 작업 중 하나가 완료되면 커널이 Node.js에게 알려주어 적절한 콜백을 poll 큐에 추가할 수 있게 하여 결국 실행되게 한다. (https://nodejs.org/ko/docs/guides/event-loop-timers-and-nexttick/) 기본적으로 스레드 갯수를 지정하지 않다면 4개의 스레드가 열린다고 한다.

 

4. Node.js는 싱글 스레드일까? 멀티 스레드 일까?

 

Nojde.js는 싱글 스레드일까? 아니면 멀티 스레드일까? 라고 질문을 받았다면 "언제?"라는 질문을 덧붙여야 한다.

 

먼저 TCP 연결의 경우를 살펴보자.

연결 당 스레드 갯수 도식화

TCP 서버를 만드는 간단한 방법으로는 1) 소켓을 생성하고 2) 소켓을 포트에 바인딩하고 3) 그 위에 "listen"을 호출하는 것이다.

int server = socket();
bind(server, 8080);
listen(server);

그리고 다른 편에서 "listen"을 호출할 때까지, 그 소켓은 연결을 하거나 연결을 수락하는 데 사용될 수 있다.

while(int conn = accept(server)) {
  pthread_create(echo, conn)
}
void echo(int conn) {
  char buf(4096);
  while(int size = read(conn, buffer, sizeof buf)) {
    write(conn, buffer,size);
  }
}

한 스레드 에서 소켓 연결이 도착해서 정보를 보내고자 할 때, 다른 소켓 연결을 받아드릴 수 없다. 그래서 동시에 다른 소켓과 연결하여 정보를 전달하고자 할 때는 다른 스레드를 사용해야한다.

 

출처 : medium.com/preezma/node-js-event-loop-architecture-go-deeper-node-core-c96b4cec7aa4

Comments