자바스크립트 런타임


싱글 스레드 언어

자바스크립트는 싱글 스레드 언어이다. 그렇기 때문에 한 번에 하나의 작업을 동기적으로 실행한다.
 
예를 들어서 이런 코드가 있다고 해보자.
이 코드는 console.log() 를 총 3번 실행시키는데, console.log('2') 는 while 문을 통해서 반복문이 종료된 뒤에 실행한다.
 
이 코드를 브라우저 콘솔에서 실행하면 이런 식으로 동작한다.
실행 결과를 보면, 먼저 1이 실행되고 while 문이 종료한 뒤 2가 실행되고 바로 3이 실행되는 것을 확인할 수 있고, 이때 페이지가 스크롤이나 클릭 등의 이벤트가 일시적으로 동작하지 않는 것도 확인할 수 있다.
이것은 자바스크립트가 싱글 스레드의 동기적 처리 방식으로 실행되기 때문이다.
 
그 다음으로는 setTimeout 을 통해서 코드를 지연시키려는 코드가 있다고 해보자.
이 코드는 setTimeout 을 통해 2를 1초 뒤에 실행하고(*) while 문을 통해서 반복문이 종료되면 3을 실행(**)하기 위해 작성되었다. 코드만 봤을 때는 1이 먼저 실행되고 1초 뒤에 2가 실행되고 2초 이상 걸리는 while 문의 반복문이 종료되면 3이 실행되는 동기적인 결과가 나올 것 같지만, 예상과는 다르게 1이 실행되고 2초 이상 뒤에 3이 실행되고 2가 실행되는 비동기적인 순서로 실행되는 것을 확인할 수 있다.
 
자바스크립트는 싱글 스레드 언어이기 때문에 하나의 작업만 실행되어야 되는데 어떻게 이런 결과가 나올 수 있을까?
 
이 과정을 설명하기 전에 먼저 관련된 용어를 정리해보려고 한다.

자바스크립트 런타임

런타임이란 프로그램을 실행할 수 있는 환경을 의미하는데 자바스크립트 런타임은 웹 브라우저와 Node.js 등이 있다.
자바스크립트 런타임은 자바스크립트 엔진과 Web APIs, 콜백 큐(Callback Queue), 이벤트 루프(Event Loop) 등으로 구성되어 있다.
notion image

자바스크립트 엔진

자바스크립트 엔진은 메모리 힙(memory heap) 과 콜 스택(call stack)으로 구성되어있다.
notion image
메모리 힙은 메모리 할당이 발생하는 곳이고 콜 스택은 코드가 실행될 때 스택 자료구조를 통해 실행 컨텍스트가 쌓이는 곳이다.

Wab APIs

Web API 는 브라우저에서 setTimeout, DOM API, fetch(AJAX) 등의 API 제공한다.

Callback Queue (=Macro Task Queue)

콜백 큐는 Script 태그나 Web API 의 항목들이 대기하는 Queue 자료구조 형식의 배열이다.

Event Loop

이벤트 루프는 콜 스택을 관찰하면서 콜 스택이 모두 비워진 경우에 콜백 큐에서 콜백 함수를 스택에 추가하여 실행한다. 이때 콜백 큐와 마이크로 태스크 큐 중에 마이크로 태스크 큐에 있는 항목을 우선적으로 콜 스택에 추가한다.

Micro Task Queue

Promise(), queueMicrotask() 가 추가되고 콜백 큐보다 우선순위가 높아 먼저 이벤트 루프를 통해 콜 스택으로 이동한다.

자바스크립트 런타임 과정

위의 예제를 과정을 통해 확인해보자.
위에서 실행했던 예제를 다시 가져왔고 Promise() 를 사용한 코드를 추가하였다. (*)
 
해당 코드는 자바스크립트 런타임 환경에서 이런 과정을 거친다.
  1. 콜 스택에 console.log('1') 이 추가된 뒤 실행되고 제거된다.
  1. 콜 스택에 setTimeout(cb) 이 추가된다.
  1. 콜 스택에서 setTimeout(cb) 이 실행되어 cb() 이 Web APIs 으로 이동하고 setTimeout(cb) 이 콜 스택에서 제거된다. (이때 Web APIs 에서 setTimeout(cb) 의 타이머가 동작한다.)
  1. 콜 스택에 Promise.resolve() 와 then(cb) 이 추가되고 cb() 은 마이크로 태스크 큐로 이동한다.
  1. 콜 스택에 slow('3') 이 추가되고 실행되어 console.log('3') 가 실행되고 제거된다.
  1. 4~5번이 실행되고 있는 동안 setTimeout(cb) 의 타이머가 완료되면 Web APIs 에 있던 cb() 이 콜백 큐로 이동한다.
  1. 마이크로 태스크 큐에 Promise() 의 cb() 이 있고 콜백 큐에 setTimeout 의 cb() 가 있는 상태에서 마이크로 태스크 큐의 우선순위가 더 높기 때문에 이벤트 루프를 통해서 Promise() 의 cb() 가 먼저 콜 스택에 추가되어 실행되고 제거된다.
  1. 콜 스택 작업과 마이크로 태스크 큐의 작업도 모두 완료되었기 때문에 이벤트 루프를 통해 콜백 큐에 있는 cb() 이 콜 스택으로 이동된다.
  1. 콜 스택에 cb() 가 추가되고 실행된 다음 콜 스택에서 제거된다.
 

 
정리를 하기 전에 자바스크립트 런타임 공부를 하면서 비슷한 의미이지만 설명하는 용어가 다 달라서 처음에는 당황했지만(콜백 큐, 매크로 태스트 큐, 태스크 큐) 정리를 하니까 어느 정도 머리의 복잡함이 해소된 것 같다.
 

 
참고