Notice
Recent Posts
Recent Comments
관리 메뉴

즐겁게, 코드

콜 스택과 이벤트 루프 본문

💬 언어/Javascript

콜 스택과 이벤트 루프

Chamming2 2021. 2. 20. 18:20

자바스크립트는 싱글 스레드 프로그래밍 언어입니다.

이는 곧 싱글 스레드 런타임(실행 환경)을 가지고 있다는 의미로, 한번에 하나의 작업만을 수행할 수 있음을 의미합니다.

자바스크립트 함수는 절대로 "동시에" 호출되지 않습니다.

그런데 웹 브라우저에서는 음악을 재생하면서 마우스 이벤트를 감지할 수도 있고, 웹 서버에 요청을 보내는 동안 새로운 키보드 입력을 수행할 수도 있는 등 동시에 일어나는 일이 꽤나 많습니다.

 

자바스크립트는 싱글 스레드에서 동작하는데 어떻게 브라우저에서 동시(병렬) 작업을 구현할 수 있었던 것일까요?

지금부터 찬찬히 알아보도록 하겠습니다.

자바스크립트 엔진과 런타임의 구조

(좌) - 자바스크립트 엔진 / (우) - 자바스크립트 런타임(브라우저)

자바스크립트 엔진은 변수와 객체의 메모리를 할당하는 힙 영역과 하나의 콜 스택으로 이루어져 있습니다.

또 자바스크립트가 실제로 실행되는 실행 환경(런타임)인 브라우저나 Node.js에는 엔진과 함께 Web API라는 요소와 태스크 큐, 그리고 이벤트 루프가 존재합니다.

콜 스택

자바스크립트는 인터프리터 언어이므로 코드를 한줄씩 읽어 나가면서 함수를 실행하는데요, 함수를 호출할 때마다 실행할 함수를 콜 스택에 순서대로 적재한 다음 스택의 *가장 위에 있는 함수부터 차례로 실행해 나갑니다.

(* 물론 동기적인 코드는 적재와 동시에 처리되며, 내부에서 호출되거나 콜백이 존재하는 경우에만 위 그림처럼 쌓이게 됩니다.)

 

그런데, 문제가 하나 있습니다.

function requestServer1() {
  for (let i = 0; i < 1000000000; i++) {}
  console.log("요청 1 완료");
}

function requestServer2() {
  for (let i = 0; i < 1000000000; i++) {}
  console.log("요청 2 완료");
}

function requestServer3() {
  for (let i = 0; i < 1000000000; i++) {}
  console.log("요청 3 완료");
}

// 세 함수의 호출은 각각 1초 정도의 시간이 걸립니다.

requestServer1();
requestServer2();
requestServer3();

console.log("요청 완료!");

위 코드에서 requestServer1, requestServer2, requestServer3 함수 모두 콜 스택에 적재된 후 호출되는데요, 세 함수는 실행 시간이 오래 걸리므로 콜 스택에도 오래 머물게 됩니다.

 

하지만 브라우저는 콜 스택이 모두 비워지기 전까지 어떤 동작도 행할 수 없어 그동안 키보드나 마우스 입력을 수행할 수 없다는 문제가 있습니다. 이는 결국 나쁜 사용자 경험으로 이어지게 되고, 이러한 문제를 해결하기 위한 기법이 바로 비동기 콜백입니다.

비동기 콜백과 태스크 큐

setTimeout, XMLHttpRequest 등의 함수는 비동기적으로 처리된다는 것을 알고 계실 것입니다.

그러나, 이 비동기 함수들이 표준 자바스크립트 명세에 포함되지 않는다는 사실은 아마 모르는 분도 계실 것입니다.

 

setTimeout이나 XMLHttpRequest 같은 함수는 자바스크립트 엔진 코드에 포함된 것이 아니라 브라우저나 Node.js 같은 런타임에 존재하는 Web API의 일부인데요, 이런 비동기 함수를 활용하기 위해 태스크 큐와 이벤트 루프가 사용되기 시작합니다.

console.log("태스크 A");

function callback1() {
  console.log("태스크 B");
}

setTimeout(callback1, 1000);

console.log("태스크 C");

콜 스택에 적재된 순서로라면 "태스크 A", "태스크 B", "태스크 C" 가 실행되는 것이라 생각할 수도 있으나, 실제로는 "태스크 A" 를 출력한 다음 "태스크 B" 가 아닌 "태스크 C" 를 먼저 출력합니다.

왜냐하면 태스크 A와 태스크 C는 콜 스택 내에 적재됨과 동시에 실행이 완료되지만, 비동기 Web API인 setTimeout은 태스크 큐 라는 별도의 공간으로 보내져 실행 순서를 기다리기 때문입니다.

 

이제 오늘의 주인공, 이벤트 루프가 활약할 차례입니다.

이벤트 루프

먼저 이벤트 루프는 자바스크립트에 포함된 요소가 아니라는 점을 명확히 짚으려 합니다.

이벤트 루프는 브라우저나 Node.js 등 런타임에만 존재하며, 콜 스택과 태스크 큐를 끊임없이(Loop) 주시하다가 콜 스택이 비었을 때 태스크 큐의 작업을 콜 스택으로 옮기는 역할입니다.

따라서, "태스크 B" 를 출력하려면 콜 스택이 완전히 빌 때까지 기다려야 하므로 더 늦게 호출되는 "태스크 C"가 "태스크 B" 보다 먼저 출력되는 것입니다.

마치며

이벤트 루프와 콜 스택, 태스크 큐에 관한 용어는 소개하는 사람마다 제각각입니다.

그래서 더욱더 혼란에 빠질 수 있는 개념인지라 다양한 자료를 참고했었는데, 가장 설명이 잘 되어있다고 느낀 영상을 함께 첨부합니다.

다만 조금 아쉬운 점이 남는다면 "브라우저와 Node.js 가 멀티스레드 환경이라면 태스크 큐 또한 둘 이상 존재할 수 있는 것인가?" 라는 질문에는 답을 찾지 못했습니다.

 

혹시 브라우저는 어떻게 멀티스레드 환경을 구성하는가(?) 에 대한 좋은 참고자료가 있다면 댓글로 남겨주시면 감사드리겠습니다. 😃

TL;DR

1. 자바스크립트 엔진은 힙 영역과 하나의 콜 스택이 존재한다. (하나의 콜 스택 = 싱글 스레드)

2. 콜 스택에는 실행할 함수들이 적재 & 수행된다.

3. 자바스크립트 런타임에는 엔진 + 태스크 큐 + 이벤트 루프가 존재한다.

4. 비동기 함수는 콜 스택이 아닌 태스크 큐에서 대기하며, 콜 스택이 비워지면 콜 스택으로 전달된다.

5. 태스크 큐의 함수를 콜 스택으로 전달하는 역할이 바로 이벤트 루프이다.

 

반응형
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆