자바스크립트 런타임


싱글 스레드 언어

자바스크립트는 싱글 스레드 언어이다. 그렇기 때문에 한 번에 하나의 작업을 동기적으로 실행한다.
 
예를 들어서 이런 코드가 있다고 해보자.
이 코드는 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() 이 있고 콜백 큐에 setTimeoutcb() 가 있는 상태에서 마이크로 태스크 큐의 우선순위가 더 높기 때문에 이벤트 루프를 통해서 Promise()cb() 가 먼저 콜 스택에 추가되어 실행되고 제거된다.
  1. 콜 스택 작업과 마이크로 태스크 큐의 작업도 모두 완료되었기 때문에 이벤트 루프를 통해 콜백 큐에 있는 cb() 이 콜 스택으로 이동된다.
  1. 콜 스택에 cb() 가 추가되고 실행된 다음 콜 스택에서 제거된다.
 

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

 
참고