Call Stack, Non-Blocking IO, Event Loop

Node를 잘 이해하기 위해서는 JavaScript의 동시성 모델을 먼저 알아야한다.
JavaScript의 실행 모델은 Event Loop, Call Stack, Callback Queue 개념으로 이루어진다.

Event Loop

이벤트 루프 모델은 여러 스레드를 사용한다.
그 중 우리가 작성한 JavaScript 코드가 실행되는 스레드를 메인스레드라고 부르며, 한 Node.js 프로세스에서 메인 스레드는 하나 이며 한 순간에 한 줄씩만 실행한다.
하지만 그 외의 일(e.g. File I/O, network)을 하는 워커스레드는 여럿이 있을 수 있다.

Call Stack

현재 시점까지 불린 함수들의 스택

function f3() {};
function f2() {};
function f1() {};
f1();

// f1() 쌓임 > f2() 쌓임 > f3() 쌓임 
// f3() 빠짐 > f2() 빠짐 > f1() 빠짐

Run-to-completion

그러면 Call Stack은 JavaScript 실행 모델과 어떤 연관이 있는가?

Call Stack은 빈 상태에서 출발을 하고 특정 요인에 의해 콜백(이자 함수)을 실행했을 때,
연쇄되는 함수들에 의해 스택이 쌓였다가 전부 빠지게 되면 다시 빈 상태로 돌아온다.
Event Loop가 다음 콜백을 처리하려면 현재 처리하고 있는 콜백의 실행이 완전히 끝나야한다.
👉 Call Stack이 완전히 비워질 때까지 처리한다는 의미

Call Back Queue (= Message Queue)

말그대로 Call Back들이 쌓이는 Queue

Call Back Queue는 앞으로 실행 할 콜백(함수와 그 인자)들을 쌓아두는 Queue이다.
콜백은 브라우저나 Node에 특정 이벤트 발생 시 메인 스레드에 이를 알려주기 위해 사용한다.
이벤트의 예시로는 파일 처리의 완료, 네트워크 작업의 완료, 타이머 호출 등이 있다.

setInterval(() => {
	console.log('잘가!');
  	while(true) {}
}, 1000)

Q.위 코드를 5초동안 실행 시 메세지는 총 몇 번 출력될까?
A. 1번 > while 루프가 도는 동안 Call Stack이 절대 비워지지 않기 때문이다.
JavaScript 실행모델에서 한 순간에 실행되는 코드는 단 하나뿐이며 Call Stack이 비워질 때 까지는 다음 콜백을 실행하지 않는다.
고로 이 동안은 Call Back Queue에서 콜백을 꺼낼 수가 없기 때문에, setInterval이 아무리 콜백을 쌓아도 메인스레드에서 실행될 수 없다.

Sync로 처리해야하는 무거운 작업들을 JavaScript의 메인 스레드에서 실행한다면 그 작업을 처리하는 동안에는 Call back Queue에서 다른 콜백을 꺼내 실행할 수 없게 된다.

👉 노드의 장점인 비동기성을 최대한으로 끌어낼 수 없으므로 이러한 Event Blocking을 피해야한다.

Non-Blocking I/O & Offloading

// Node에게 파일 읽기를 요청 후
// 워커 스레드에서 파일을 읽기 시작

fs.readFile(fileName, (err,data) => {
  	// Node가 파일을 모두 읽으면
  	// 1. 콜백큐의 이 함수에 err, data 인자를 채우고
  	// 2. 콜백큐에서 꺼내질 때 이 부분이 실행 됨
});

// readFile의 호출이 끝난 직후 바로 이 함수를 실행
// 이는 여전히 같은 콜백을 처리하는 중이기 때문
someTask();

브라우저나 Node.js에서나, Web API 혹은 Node API의 동작이 끝나면 콜백큐에 등록한다.
브라우저나 Node가 요청 받은 일을 하고 있는 동안, 메인스레드와 Event Loop는 영향 받지않고 계속 실행된다.

👉 이를 Offloading이라고 하며, Node 서버의 메인스레드가 하나임에도 불구하고 빠르게 동작할 수 있는 이유가 된다.

좋은 웹페이지 즐겨찾기