moon palace

node.js는 single-thread 인가요? 본문

JavaScript

node.js는 single-thread 인가요?

Hdaleee 2021. 2. 10. 01:57

들어가면서

"node.js는 single-thread 인가요?"

이 간단하면서도 어려운 질문을 이해하기 위해서 많은 글을 읽어왔습니다. 조금 이상한 성격때문인지 글을 읽을수록 더 궁금해지는게 늘어갔고, 결국은 기초적인 컴퓨터에 대한 이해조차 없었다는 것은 깨닫게 되었죠.
간단한 질문에서 시작되었지만 결국은 원점으로 돌아가게 했던 질문을 천천히 되짚어보면서 개념을 정리해보려고 합니다. 누군가 이 글을 보는 분이 있다면 도움이 되길 바라며, 날카로운 지적과 크리틱은 언제나 환영입니다.

 


1. CPU와 프로세스 그리고 스레드

저 궁극적인 질문에 도달하기 위한 첫번째 단계로, 우선 컴퓨터는 어떻게 구성되어 있는가를 이해할 필요가 있습니다.
컴퓨터라는 것은 크게  연산장치와 기억장치, 입출력장치로 이루어져 있는데요.

연산장치
 - CPU : 중앙 처리 장치(모든 연산을 컨트롤)
 - GPU : 그래픽 처리 장치
 - FPGA / ASIC: 집적회로 반도체(디테일한 설명은 안될과학의 영상으로 대체!)

기억장치
 RAM / SSD / EDD 등 메모리장치

입출력장치
키보드 / 마우스 / 모니터 / 프린터 등

위 여러 구성품들 중에서 우리가 집중해야하는 것은 당연히 CPU이다. 이 CPU는 기억, 해석, 연산, 제어 모든 기능을 수행하는 중앙 처리 장치인데, 사람의 뇌와 자주 비교되죠.

다음으로 이 CPU가 어떻게 기능들을 수행하는지를 알아보려면 프로세서와 스레드에 대한 이해가 필요합니다. 프로세스는 프로그램의 ing 버전이라고 볼 수 있어요. 프로그램은 실행될 준비가 완료된 코드들의 묶음(다들 webpack 알쥬?)이고, 이 프로그램들이 실행되고 있어서 CPU의 처리 과정(영어로 process죠?)에 속하게 되면 그때부터 프로세스가 되는거죠. 그렇다면 스레드는 무엇일까요? 스레드란 CPU가 처리할 업무들이 쌓여있는 리스트라고 볼 수 있습니다. 안될과학의 영상에서는 CPU속 '코어'라는 노동자의 팔 갯수라고 설명하고 있는데, 나는 스레드를 특정 기준에따라 구분해 둔 처리해야하는 업무들의 리스트라고 생각했습니다. 더 이해하기 수월한 쪽으로 생각하시고 읽어내려가면 되겠네요!

2. 싱글스레드와 멀티스레드

짐작하겠지만 싱글스레드는 하나의 스레드만 가지는 방식, 멀티스레드는 2개이상의 스레드를 가지는 방식을 이야기합니다. 동시에 처리할 수 있는 리스트가 많을 수록 일을 잘할 것이라고 생각할 수 있지만 사실 꼭 그런것은 아니죠. 처리해야하는 업무의 내용이나 방식에 따라 더 효율적인 방식이 있을 뿐, 어느것이 더 좋고 나쁜 개념이 아니라는 것을 알아야합니다. 싱글스레드와 멀티스레드는 업무를 처리하는 방식의 차이일 뿐이다!

왜 node.js는 single-thread인가에 대한 이야기를 해야하기 때문에 "node.js는 어떻게 일을 처리하는가"에 대한 관점으로 설명을 해볼게요. 과연 node.js는 무엇일까요?
node.js는 크롬을 통해 공개된 오픈소스인 자바스크립트 v8 엔진(자바스크립트언어를 파싱/컴파일 한다.)에 비동기 이벤트 처리 라이브러리(libuv)를 결합해서 탄생했습니다. 이는 크롬이라는 브라우저 밖에서도 v8엔진을 통해 CPU가 자바스크립트 언어를 이해할 수 있도록 했고, 이는 하나의 언어로 프론트와 백엔드를 모두 제어할 수 있다는 엄청난 장점(물론 I/O처리에 있어서 성능도 좋았구요!)을 가지고 폭발적인 인기를 누렸습니다. 

벌써 궁극적인 질문인 "왜 node.js는 single-thread인가요?"의 해답이 지나갔는데, 축약해서 이야기하자면 맞습니다, 비동기 이벤트 처리를 기반으로 하는 node.js에 더 적합한 방식은 싱글스레드방식이기 때문이에요. 이 부분을 이해하기위해서는 또 다른 단어를 정확하게 이해해야하는데 바로 "비동기 이벤트 처리"라는 부분입니다. 도대체 비동기 이벤트 처리는 어떻게 이루어지길래 싱글스레드방식이 더 적합한걸까요? 관련한 내용의 아주 재미있고 직관적인 해답은 맨 위 유튜브 영상에 담겨있습니다. 그렇다면 비동기 이벤트 처리는 무엇일까요?

3. 이벤트 루프

(function() {
	console.log(1);
	setTimeout(function(){console.log(2)}, 1000);
	setTimeout(function(){console.log(3)}, 0);
	console.log(4);
})(); 

위 익명함수를 실행하면 콘솔에 어떤 순서로 숫자가 찍힐까요?

정답은 1, 4, 3, 2 입니다. 눈치가 좋으신분들은 setTimeout이 전부 더 늦게 처리되었다는 사실을 알아내셨을 텐데요, 이 결과와 아래 그림을 가지고 이벤트 루프를 함께 살펴볼게요.

https://developer.mozilla.org/ko/docs/Web/JavaScript/EventLoop

MDN에서는 위 그림과 함께 자바스크립트 엔진의 구조를 설명하고서 이벤프루프를 설명합니다. 스택 아래에 큐를 바로 배치한 점이 이해도를 돕는다고 생각해서 이 이미지를 가지고 왔습니다. 

우선 Heap이라는 부분은 let, const, var 등으로 변수를 할당했을 때 그 값들이 저장되는 메모리(그렇다는건 Heap은 아직 저장되지 않은 공간이라는 것이겟죠?)입니다. 
두번째로 Stack이라는 곳에서는 자바스크립트에서 호출되는 함수들의 스택입니다. Stack은 위에서부터 처리됩니다.
마지막으로 Queue가 스택의 아래쪽에 맞물려 위치(처리 순서의 이해를 돕기위해 시각화 한 것)해 있습니다. Queue는 먼저 들어온대로 처리되니까 왼쪽부터 처리됩니다. 이는 스택이 비었을 때, Queue의 작업이 하나씩 Stack으로 들어가면서 작업이 처리된다는 뜻이죠.

지금쯤 새로운 궁금증이 생깁니다. 도대체 Stack과 Queue로 보내는 기준은 무엇이며 누가 어떻게 처리하는 것인가!? 그리고 이벤트 루프는 아직 등장하지도 않았다!!(그렇다는건 이제 등장할 때라는거죠?)

위에서 node.js는 v8엔진과 libuv가 합쳐져서 만들어졌다고 했죠. 그리고 분명히 위의 자바스크립트 엔진의 그림에는 이벤트루프가 존재하지 않습니다. 그렇다는 건 libuv에 이벤트 루프가 존재한다는 것을 유추할 수 있다는 것이죠!
실제로 Node.js의 구조를 보면 이벤트루프는 libuv내에 존재합니다.(질문이 "node.js가 single-thread인가요?" 였기때문에 브라우저 환경에서의 비동기 처리는 libuv와 조금 다를 수 있습니다.)

https://stackoverflow.com/questions/10680601/nodejs-event-loop

libuv 라이브러리에 해당하는 이벤트가 들어오면 libuv가 해당 이벤트를 Queue에 넣고 Stack이 비었는지 확인하여 Queue에 대기중에 이벤트를 Stack으로 넣어줍니다. 이 것이 바로 이벤트 루프입니다. MDN에서는 이벤트 루프를 이렇게 표현(가상코드)하고 있습니다.

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

4. 요약

위 내용을 바탕으로 정리해보면 다음과 같습니다.

node.js 자체는 single-thread 방식이 아니다. 자바스크립트를 실행하는 부분에서만 single-thread로 구성되어있고, 이 때의 작업 동시성은 '이벤트 루프'를 통해서 구현할 수 있다.

물론 위 설명에서 조금 모자란 부분이 있습니다. 각 브라우저 환경마다 webAPI의 차이가 있고, 그렇기때문에 실행되는 이벤트 순서가 다를 수 있죠. 게다가 Queue는 그냥 하나로 통으로 존재하는 것이 아닙니다. Microtask Queue / Animation Frames / Task Queue 가 존재하고 순서대로 실행된다. 각 이벤트가 어느 Queue에 해당하느냐에 따라 순서가 달라진다는 것! Queue에 대한 더 자세한 내용은 다음에 한번 더 따로 다루어야 겠네요!


참고 링크

Comments