JavaScript/JS를 파헤쳐보자

Node.js는 완전한 싱글 스레드일까?

동구름이 2024. 7. 26. 02:19

Worker Thread와 의문의 시작

 worker thread를 만나고 오늘 하루가 고되기 시작했다. 아래 내용들 때문인데, 자연스럽게 Node의 이벤트 루프와도 연관지어 생각해볼 수 있어서 좋았다.

 

 우선, Worker Thread는 이름 그대로 작업을 진행하는 스레드를 말한다. Worker Thread를 통해 메인 스레드와 분리할 수 있고, 그런 점으로 인해 복잡한 로직을 다른 곳에서 처리할 수 있게 해서 사용성을 증가시킬 수 있다.

 

자바스크립트는 단일 스레드 아닌가?

 그런데 오늘 구현을 하면서도 들었던 의구심이 한 가지 있었는데 자바스크립트는 단일 스레드를 사용하는 것으로 알고 있었다. 그런데 어떻게 스레드를 하나 더 사용할 수 있을까 라는 생각이었다.

 

 

이때 간과했던 점이 한 가지가 있었다. 자바스크립트로 코드를 짜더라도 동작은 node.js 엔진 위에서 돌아간다는 것이다.

 

근데 또 생각해보니 node.js는 싱글스레드 논블로킹라는 말을 많이 들었다. 그럼 대체 worker thread는 어떻게 스레드를 처리하는거지..?? 의구심이 들었다.

 

그래서 node.js의 동작을 한번 살펴보기로 했다.

 

 

Node.js는 완전한 싱글 스레드일까?

Node.js는 싱글스레드 논블로킹 모델이라고 흔히 소개된다.

 

하나의 스레드로 동작하지만, 비동기 I/O 작업을 통해 요청들을 서로 블로킹하지 않는다. 즉, 동시에 많은 요청들을 비동기로 수행함으로써 싱글스레드일지라도 논블로킹이 가능하다는 것이다.

 

자바스크립트는 단일 스레드인데, 어떻게 이것이 가능할까?

 

 

자바스크립트 V8 엔진

우선 자바스크립트의 V8 엔진을 살펴보자(이전에도 다루었지만 Node.js도 V8 엔진 기반이다)

V8 엔진의 주요 구성요소는 Memory Heap과 Call Stack이다.

 

Memory Heap은 메모리 할당이 일어나는 곳이고, Call Stack은 코드 실행에 따라 호출 스택이 쌓이는 곳이다.

 

 이 부분과 관련된 내용은 이전 포스팅에서 다루었다.

(이전 포스팅을 보면, 자바스크립트 엔진에서 왜 두 가지가 주요한 요소인지 이해할 수 있을 것이다!)

 

 

 다시 엔진으로 돌아와서 얘기를 하자면 자바 스크립트는 싱글 스레드기반 언어이다. 그래서 호출 스택은 하나다! 그래서 한번에 한 작업만 처리할 수 있다.

 

호출 스택이 하나밖에 없는데, 엄청나게 처리해야할 것이 큰 함수가 들어오면 어떻게 될까?

 

 모두 멈춰버리고 해당 함수가 끝나기만을 기다릴 것이다. 그렇게 되면, 실제로 화면을 표시할 때 사용자들은 먹통된 화면만 바라보는.. 사용자 경험에 큰 악영향을 주게 된다.)

 

 

Javascript 엔진을 구동하는 런타임 환경(Node.js)

그런 큰 단점이 있기 때문에, 위와 같이 Javascript의 런타임이 아닌, Javascript 엔진을 구동하는 런타임(Runtime) 환경에서 Javascript의 런타임 외에 외부 런타임 환경들이 조합된다.

 

여기서 Javascript 엔진을 구동하는 런타임(Runtime) 환경이 Node.js, 브라우저 등을 말하는 것이다!

그리고 이런 외부 런타임 환경을 통해 비동기 작업논블로킹 I/O이 가능하게 된다.

 

 

그래서 Node.js는 싱글스레드 논블로킹 모델이라는 것은 Node js에서 돌아가는 핵심 구조가 자바스크립트 엔진과 이벤트 루프이고 그것을 실행하는 것이 싱글스레드이기 때문에 붙여진 말이라는 의견이 대다수이다.

 

 

 

 다시 돌아와 이런 외부 런타임 환경이 어떻게 싱글 스레드인 자바스크립트 엔진을 비동기 작업과 논블로킹 I/O 등이 가능하게 만드는 것일까??

 

 

이 부분에 있어 이해에 정말 큰 도움이 된 영상이 있다. 유튜버 얄코님의 `비동기프로그래밍이 뭔가요?` 영상이다.

https://www.youtube.com/watch?v=m0icCqHY39U&t=406s

 

4분 6초부터 해당 내용이 나온다.

 

 

위 영상에서도 확인할 수 있듯이 브라우저 환경에서는 Web api가 주된 역할을 한다. (이벤트 루프와 관련된 포스팅은 이후 다룰 예정이다)

 

Web api가 지원하는 비동기 작업을 수행하는 코드가 실행되는 과정은 아래와 같다.

  1. 코드가 호출스택에 쌓인 후 실행되면, Javascript의 엔진은 비동기 작업을 Web api에게 위임한다.
  2. Web api는 해당 비동기 작업을 수행하고, 콜백 함수를 이벤트 루프를 통해 콜백 큐에 넘겨준다.
  3. 이벤트 루프는 콜스택에 쌓여있는 함수가 없을 때, 콜백 큐에서 대기하고 있던 콜백함수를 콜스택으로 넘겨준다.
  4. 콜스택에 쌓인 콜백함수가 실행되고, 콜스택에서 제거된다.

 

Worker도 JS가 제공하는 기능이 아닌, Web API 기능을 사용하기 때문에 브라우저에서 제공할 수 있는 추가 스레드를 이용할 수 있다!

 

 

정리: Javascript의 특징인 비동기, 동시성, 논블로킹(Non Blocking) I/O?

그럼 다시 돌아와 흔히 말하는 자바스크립트의 특징을 정리해보자.

 

비동기는 여러 작업들이 동시에 진행될 수 있도록 하는 프로그래밍 방식을 말하고, 동시성은 여러 작업이 동시에 실행되는 것이다. 논블로킹 I/O는 I/O 작업이 완료될 때까지 프로그램의 실행을 차단하지 않는 방식이다. 작업이 실행되는 동안 다른 작업을 계속할 수 있다.

 

위 특징들을 보면 자바스크립트가 단일 스레드라는 것과는 상반된 개념이다. 모두 멀티스레드와 연관된 개념이기 때문이다.

 

하지만 자바스크립트 엔진을 감싸는 외부 런타임 환경까지 포괄하면 이해가 된다.

 

 

참고자료

https://helloinyong.tistory.com/350
https://medium.com/hcleedev/web-web-worker-사용법과-주의할-점-webpack-메모리-문제-테스트-모킹-2d77c5b23afe
https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/
https://medium.com/@vdongbin/javascript-작동원리-single-thread-event-loop-asynchronous-e47e07b24d1c
https://medium.com/@vdongbin/node-js-동작원리-single-thread-event-driven-non-blocking-i-o-event-loop-ce97e58a8e21