자바스크립트의 이벤트 루프와 콜스택의 동작원리에 대해 알아보다가 발견한 좋은 블로그 글들이 많아 참고하였고 정리해보기로 했다. 조금 어렵지만 내가 사용하는 자바스크립트의 기본 원리에 대한 내용이라 유용한 것 같다.
자바스크립트는 기본적으로 싱글스레드로 한번에 한가지 작업만 처리할 수 있다.
메모리 힙과 콜 스택으로 구성되어 있는데 힙에선 우리가 선언한 변수, 함수 등의 메모리 할당이 일어나고 콜 스택에 코드가 실행될 때 우리가 호출한 작업들이 쌓이게 된다.
이렇게 하나의 콜스택에 작업을 쌓아두고 처리한다. 예외 발생 시 자주 볼 수 있는 stack trace는 에러가 났을 때 콜스택의 단계를 의미하는 것이다.
또한 무한 재귀 루프와 같이 계속해서 스택에 작업이 쌓이게 되는 경우 호출 스택의 최대 허용치를 넘게 되면 에러가 발생한다.
싱글 스레드는 멀티스레드 환경에서 제기되는 데드락, 임계 지점 등의 문제를 고민하지 않아도 되어 쉬운 이점이 있으나, 호출 스택이 하나뿐이므로 제약이 따른다.
만약 호출 스택에 이미지 프로세싱과 같이 시간이 엄청 오래 걸리는 함수가 있다면 어떻게 될까?
해당 함수가 실행되는 동안 브라우저는 다른 작업을 하지못하고 대기 상태가 될 것이다. 페이지를 그리지도 못하고, 코드를 실행할수도 없고 가만히 멈춘 상태가 된다. 매끄럽고 자연스러운 UI를 원한다면 매우 문제가 될것이다. 이는 다음에 나올 비동기 콜백으로 해결할 수 있다.
싱글스레드로 단일 호출 스택을 사용하는 자바스크립트 엔진은, 요청이 들어올때마다 요청을 순차적으로 호출 스택에 담아 처리한다. 그렇다면 비동기 요청과 동시성은 어떻게 처리할 수 있는 걸까?
이는 자바스크립트 엔진을 구동하는 환경, 브라우저나 Node.js가 담당하게 된다(런타임이라고도 하는 것 같다).
비동기를 위해 호출하는 setTimeout, XMLHttpRequest 등은 자바스크립트 엔진이 아닌 Web API 영역에 정의되어 있다. 또한 이벤트 루프와 태스크 큐와 같은 장치도 자바스크립트 엔진 외부에 구현되어있다.
이 Web API에서는 콜스택에 비동기 호출 함수를 만나게 되면 이 함수를 가져다가 콜백 큐에 밀어넣는다.
콜백 큐에는 이렇게 비동기 함수 호출이 계속 쌓이게 된다.
그러다 콜스택에서 모든 작업이 마무리되고 콜스택이 비어있게 되면 이벤트 루프가 일을 한다.
이벤트 루프는 콜스택이 비면 콜백 큐의 첫번째 작업을 콜스택으로 밀어넣고 이를 반복하는데 이를 틱(tick)이라고 한다.
이때 콜스택이 비어있어야 콜백 큐의 작업을 콜스택으로 옮겨 처리할 수 있기 때문에 setTimeout의 시간을 0으로 지정하더라도 곧바로 실행되는것을 보장하지 못한다고 한다.
자바스크립트 엔진이란 자바스크립트 코드를 실행하는 프로그램 또는 인터프리터를 말한다.
이러한 엔진은 다양한 종류가 있는데 대표적으로 구글의 V8 엔진이 있다. 그 외에 Mozilla FireFox에서 사용하는 SpiderMonkey, 마이크로소프트의 Edge 브라우저에 사용되는 Chakra, 애플에서 개발하고 Safari, React Native App에서 사용되는 JavaScript Core가 있다.
자바스크립트 엔진의 공통적인 과정은 자바스크립트 코드를 parser를 거쳐 Abstract Syntax Tree로 바꾼 후, 이것을 interpreter가 bytecode로 변환하게 된다.
인터프리터를 사용하는것까지는 알고있던 내용이지만 출처 블로그를 참고하니 이 이후에optimizing compiler를 거쳐 최적화를 진행한다. 이곳에서는 프로파일링 데이터를 기반으로 최적화된 기계어를생성하는데, 정확하지 않은 결과가 나왔다면 다시 deoptimize를 해 바이트코드로 되돌린다고 한다.
이렇게 parser → interpreter → optimizing compiler를 거치는 과정이 자바스크립트 엔진에서 공통적으로 진행되는 파이프라인이고 안에서 최적화하는 과정은 엔진마다 다르다.
인터프리터는 최적화하지않은 바이트코드를 빠르게 생성한다.
최적화 컴파일러는 최적화된 기계어 코드를 약간 시간을 들여서 생성한다.
이 과정에서 바이트코드는 중간언어(Intermediate representation)로 interpreter 모드에서는 바이트코드를 하나씩 읽어 실행하고, JIT 모드에서는 바이트코드를 기반을 컴파일하여 수행한다고 한다.
여기서 바이트코드와 JIT는
바이트코드(Bytecode, portable code, p-code)는 특정 하드웨어가 아닌 가상 컴퓨터에서 돌아가는 실행 프로그램을 위한 이진 표현법이다. 하드웨어가 아닌 소프트웨어에 의해 처리되기 때문에, 보통 기계어보다 더 추상적이다.
JIT 컴파일(just-in-time compilation) 또는 동적 번역(dynamic translation)은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다. 이 기법은 프로그램의 실행 속도를 빠르게 하기 위해 사용된다.
라고 한다.
캡틴 판교님 번역 : 자바스크립트의 동작원리: 엔진, 런타임, 호출 스택
https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/#들어가기
원본: https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
자바스크립트 비동기 처리와 콜백 함수
https://joshua1988.github.io/web-development/javascript/javascript-asynchronous-operation/
JSConf의 이벤트루프 영상
NHN 이벤트 루프
이벤트루프의 동작원리와 microtask등 고민하신 블로그