Call Stack, Event loop, Task Queue

자바스크립트와 V8 엔진

  • 자바스크립트는 Chrome V8 엔진을 사용하여 동작한다.
  • JIT(just-in-time compilation) 컴파일 방식으로 프로그램을 실제 실행하는 시점에 기계어로 번역하게 된다.

JIT 컴파일이란?

동작 원리

자바스크립트는 싱글 스레드 기반으로 하나의 콜 스택(Call Stack)과 하나의 메인스레드를 가진다. 즉, 한번에 하나의 일만 처리할 수 있다.

그렇다면 자바스크립트의 비동기 처리는 과연 어떻게 이루어질까?

자바스크립트는 다음과 같이 구성되어 있다.

  • Call Stack
  • Web APIs
  • Task Queue (Callback Queue)
  • Event loop

Call Stack

콜스택은 코드 실행에 따라 스택 프레임이 쌓이는 공간이다.

함수가 호출되면 콜 스택에 하나씩 쌓이게 된다. 콜 스택은 후입 선출(LIFO)로 실행되며 위에 쌓인 함수부터 하나씩 처리해 간다.

Web APIs

Web APIs는 자바스크립트 엔진이 Ajax 요청, setTimeout(), 이벤트 핸들러의 등록과 같이 웹 브라우저에서 제공하는 기능들을 말한다.

  1. 자바스크립트 엔진의 콜 스택에서 실행된 비동기 함수가 요청하는 비동기 작업에 대한 정보와 콜백 함수를 웹 API를 통해 브라우저에게 넘긴다.
  2. 브라우저는 전달 받은 요청을 별도의 스레드에 위임한다.
  3. 해당 요청이 완료되면 콜 스택으로 부터 전달받은 콜백 함수를 다음에 설명할 Task Queue에 전달한다.

예시

setTimeOut(10sec)이 담긴 Timer 함수를 실행했다고 가정하자.

  1. 콜 스택에 Timer 함수가 담기고 실행된다.
  2. 콜 스택은 Web API를 통해 10초라는 지연 시간이 담긴 정보와 콜백 함수를 브라우저에 전달한다.
  3. 브라우저는 10초가 지난 후 Task Queue에 콜백 함수를 전달한다.

처음엔 10초라는 지연 시간을 브라우저가 아닌 Task Queue에 담긴 상태에서 기다린다고 오해했다. 브라우저에서 10초를 기다리는 것과 Task Queue에서 기다리는 것은 프로그램의 실행 순서에서 확연한 차이를 보이기 때문에 정확히 알아두어야 한다.

Task Queue

태스크 큐는 Callback Queue 또는 Event Queue 라고도 부른다.
Web API에서 보내진 비동기 처리들이 모이게 되고 콜 스택으로 넘어가 처리되기를 기다리는 곳이다. 선입선출(FIFO) 방식으로 실행된다.

태스크 큐의 작업들은 콜 스택이 비게되면 하나씩 전달되어 진다.

Event loop

이벤트 루프는 콜 스택과 태스크 큐를 감시한다.

콜 스택이 비게되면 태스크 큐에 쌓인 비동기 작업들 중 가장 먼저 들어온 작업을 콜 스택에 전달한다. 이벤트 루프를 통해 비동기 처리 순서가 정해진다.

참고:
JavaScript | 자바스크립트 작동 원리(Event Loop와 Call Stack, Web API, Callback Queue)


실행 예시

setTimeOut 처리

만약, 여러 개의 setTimeOut이 발생하면 어떻게 처리되는 지 확인해보자.

프로그램의 시작 이후에 클릭 이벤트가 발생했다고 가정한다.

console.log("start");

document.body.addEventListener("click", () => {

    console.log("callback start");

    setTimeout( ()=> { 
      console.log("1");
      setTimeout( ()=> {
        console.log("3");
      },10);
    },1);


    setTimeout( ()=> { 
      console.log("2");
    },1000);

    console.log("callback end");
});

console.log("end");

실행 분석

  1. 가장 먼저 콜 스택에 다음과 같은 코드가 담긴다. 두 개의 console.log가 먼저 실행되어 startend가 출력되고 클릭 이벤트에 대한 정보와 콜백 함수가 Web API에 전달된다.

  2. 브라우저에서 클릭 이벤트가 발생하면 태스크 큐에 해당 콜백 함수를 전달한다.

  3. 이벤트 루프는 현재 콜 스택에 비워져있다는 것을 확인 후 해당 콜백 함수를 태스크 큐에서 꺼내 콜 스택에 전달하고 실행된다.

  4. 콜백 함수가 실행되어 callback start, callback end가 출력 된다. 첫 번째 setTimeout(콜백 함수에 세 번째 setTimeout이 담겨져 있다.) 두 번째 setTimeout이 Web API에 전달되어 브라우저에서 각각 1ms, 1000ms를 기다린 후 태스크 큐에 전달된다.

  5. 1ms가 지난 후 태스크 큐에 담기고 이벤트 루프는 동일하게 콜 스택의 상태를 확인 후 전달한다.

  6. 콜 스택에서 첫 번째 콜백 함수가 실행되면 1이 출력되고 브라우저에 10ms의 지연 시간을 가진 setTimeout의 정보가 추가적으로 전달된다.

  7. 기존에 담긴 1000ms의 시간이 지나기 전에 세 번째 setTimeout인 10ms가 더 빠르게 실행되어 세 번째 콜백 함수가 먼저 태스크 큐에 담기고 콜 스택에 전달되어 3을 출력한다.

  8. 1000ms가 지난 후 두 번째 콜백 함수가 태스크 큐에 담기고 콜 스택에 전달되어 2를 출력한다.

실행 결과

회고

이전에 setTimeout의 지연 시간을 Web API가 아닌 태스크 큐에서 기다린다고 생각했다.

이때의 실행 결과는 태스크 큐에 1번과 2번 setTimeout이 쌓인 상태에서 시간을 기다리게 되고 1번이 실행되면 3번이 태스크 큐에 쌓이게 된다.

하지만 2번이 먼저 태스크 큐에서 기다리고 있기 때문에 태스크 큐의 선입선출(FIFO) 방식으로 1->2->3 순서로 실행된다. 자바스크립트의 실행 순서와는 전혀 다르게 실행되는 모습이다...

오답을 겪은 것이 자바스크립트 엔진을 더 공부해보고 확실하게 알 수 있는 계기를 만들어 주었다. 자신이 사용하는 언어를 이해하고 사용하는 것이 중요함을 깨달았다.

크롱님 감사합니다👍

좋은 웹페이지 즐겨찾기