자바스크립트 는 흔히 싱글스레드로 동작하는 언어라고 한다.
이를 이해하기 위해서는 먼저 프로세스와 스레드에 대해 이해할 필요가 있다.
프로세스는 운영체제에서 할당하는 작업의 단위이다.
쉽게 말해 프로그램이 실행되고 있는 상태 혹은 컴퓨터가 일을 하고 있는 상태를 말한다.
노드나 웹 브라우저와 같은 프로그램은 개별적인 프로세스이며, 프로세스 간에는 메모리 등의 자원을 공유하지 않는다.
(자원을 공유했다간,,, 상상만 해도 끔찍하다)
스레드는 프로세스 내에서 실행되는 흐름의 단위이다.
예를 들어보자. 웹 브라우저는 하나의 프로그램이고 브라우저가 돌면서 하나의 프로세스가 진행된다.
브라우저가 일을 할 때도 영화를 다운받으며 다른 페이지를 돌아다닐 수 있어야 하고, 유튜브 영상 데이터 받아오면서 받아진 데이터로 영상 재생도 가능해야한다.
이렇게 한 프로세스 안에 여러갈래의 작업들이 동시에 진행 될 필요가 있는데, 이 갈래가 바로 스레드(Thread)이다.
컴퓨터의 운영체제는 프로세스마다 자원을 분할해서 할당하는데,
프로세스들은 할당받은 컴퓨터의 자원을 분할해서 사용하고,
스레드는 프로세스마다 주어진 자원을 함께 사용한다.
- 동시성 (concurrency): 프로세서 하나가 이거 조금하고 저거 조금하고 요거 조금하면서 여러 작업을 돌아가면서 일부분씩 진행 => context switching ( 각 스레드가 번갈아가며 실행 )
즉, 동시에 실행되는 것 처럼 보이는 것.- 병렬성 (parallelism): 프로세스 하나에 코어 여러개가 달려서 각각 동시에 작업을 수행 (듀얼코어, 쿼드코어, 옥타코어 등 멀티코어 프로세서가 달린 컴퓨터에서 할 수 있는 방식)
즉, 물리적으로 정확히 동시에 실행되는 것.
자바스크립트는 싱글 스레드로 동작하기 때문에
하나의 요청이 들어오면 그 요청에 대한 응답을 하기 전까지 다음 동작을 수행하지 않는다.
다음 그림을 보자.
자바스크립트의 엔진인 V8이 실행되면 메모리를 할당하는 메모리 힙과 코드가 호출되며 스택으로 쌓이는 콜스택이 생성된다. 즉, 하나의 메인스레드와 하나의 콜스택을 갖기 때문에 자바스크립트가 싱글스레드 기반의 언어라 불리는 것이다.
✅ 자바스크립트 엔진: 자바스크립트 코드를 실행하는 프로그램 또는 인터프리터
❓❓이렇게 동기적으로 코드를 처리한다면, 그 유명한 자바스크립트의 비동기는 어떻게 처리하는 걸까?
JavaScript 엔진을 구동시켜주는 런타임 환경이 바로 이러한 비동기 작업을 대신 해준다.
런타임 환경에는 웹 브라우저와 Node.js 가 있다.
그 중 Node.js를 살펴보자.
Node.js 는 자바스크립트의 런타임이다. 런타임이란 특정 언어로 만든 프로그램들을 실행할 수 있는 환경을 말한다.
즉, Node는 자바스크립트를 실행시켜주는 환경이라고 할 수 있다.
기존 자바스크립트는 웹 브라우저 위에서만 실행 할 수 있었지만, 노드가 나온 뒤로 말이 달라졌다.
위 그림은 노드의 내부 구조를 나타낸 것이다.
동기적으로 동작할 수 밖에 없던 자바스크립트가
논블로킹 I/O 모델과 이벤트 루프를 구현한 libuv 라이브러리와 합쳐져
비로소 비동기적인 작업을 수행할 수 있게 되었다.
(비동기적인 작업은 libuv 라이브러리에게 맡기는 것이다)
이로 인해 Node.js는 싱글스레드-논블로킹 모델을 구현할 수 있다.
하나의 스레드로 동작하지만, 비동기 I/O 작업을 통해 요청들을 서로 블로킹하지 않는다.
즉, 동시에 많은 요청들을 비동기로 수행함으로써 싱글스레드일지라도 논블로킹이 가능하다.
Node.js에서 동작하는 이벤트 루프는 libuv 내에서 구현된다.
Node.js는 싱글스레드이기 때문에 하나의 이벤트 루프를 갖으며, 하나의 스레드가 모든 것을 처리한다.
V8엔진이 비동기 작업이 발생하면 노드는 libuv에 비동기 요청을 보낸다.
libuv는 OS 커널이 어떤 비동기 API를 지원하는지 알고있기 때문에
OS 커널에서 처리가 가능한 비동기 작업이라면 커널에게 요청을 보내고 응답을 받아
우리에게 비동기 응답을 준다. 하지만 OS 커널에서 지원하지 않는 비동기 작업이라면
자신만의 워커스레드가 담긴 스레드 풀에 요청을 보낸다.
Node.js가 비동기 작업을 처리하기 위한 구현체로서 EVENT LOOP가 존재한다.
스레드 풀 또한 마찬가지로 해당 작업을 수행하면, 이벤트 루프에 콜백함수를 전달하는데 Node.js는 이 이벤트 루프를 순회하며 요청에 대해 처리한다.
다음은 이벤트 루프가 순회하는 6가지 단계이다.
Timer Phase
Pending Callbacks Phase
Idle, Prepare Phase
Poll Phase
Check Phase
Close Callbacks Phase
Node.js의 코드가 어떤 순서로 동작하는지 이해하기 위해서는 이벤트 루프에 대한 이해가 필수적이다. 이벤트 루프에 대한 자세한 내용은 이 게시물을 참고하자.
[참고자료]