일반적인 프로그래밍 코드의 구동원리부터 살펴보면,
가장 대표적인 예로는
구글에서 만든 V8엔진으로 Chrome에서 사용 중이며 현재 노드js의 런타임으로도 사용된다.
(V8엔진 내 AST에서 인터프리팅 작업을 통해 코드를 한줄마다 순차적으로 실행)
그 외 Rhino, SpiderMonkey, KJS etc…
Memory Heap : 매모리를 할당하는 곳
Call Stack : 실행 순서에 따라 호출스택이 쌓이고 나가는 곳
싱글스레드 기반, 콜백큐와 이벤트루프 사용
결론부터 말하자면, 자바스크립트 런타임환경은 싱글스레드를 통한 동기방식과 이벤트루프를 통한 비동기의 혼합으로 이루어져있다.
그림에서 알 수 있듯이 싱글스레드는 호출스택이 하나이기 때문에 작업을 하나씩만 처리할 수 있다.
후입선출 구조로 함수를 호출할 경우 순차적으로 실행될 명령들(연관된 함수 집단)이 콜스택에 쌓이게 되고 쌓인 순서와는 반대로
나중에 들어온 명령이 먼저 실행되게 된다.
호출스택의 크기에는 한계가 있으며 호출스택이 최대크기가 되면 ‘스택 날려버리기’가 일어나며 에러가 발생한다.
대표적으로 자기 자신을 호출하는 재귀함수의 경우 반복실행을 하는 동안 스택이 비워지지
않은채 쌓여가기 때문에 ‘스택 날려버리기’가 발생할 위험이 있다.
이와같이 싱글스레드 기반의 구조는 여러작업을 동시에 실행하고 싶은 경우 명령마다 딜레이가 발생하는 제약이 존재한다.
이러한 제약에서 벗어나 연산량이 많은 코드를 지연없이 동시에 실행하는 방법이 비동기 방식이며 이러한 비동기처리는 콜스택 외부에서 실행되게 된다.
순서를 살펴보면, 호출스택에 들어온 명령이 비동기적 API(AJAX, setTimeout… ) 혹은 비동기함수(promise, callback)일 경우
호출스택에 쌓이자마자 빠져나가며 WEB API를 위한 비동기 환경으로 이동해 비동기적으로 동시에 처리된다.
처리된 비동기 명령은 결과값을 콜백큐(callback queue)(선입선출구조) 에 가지고 있다가
이벤트루프(event loop)에 의해 호출스택이 비워진 타이밍에 순차적으로 호출스택으로 들어가 결과값으로 호출된다.
사실 이러한 과정 중 콜백큐에서의 동시적인 함수처리가 가능한 이유는 호출스택 외부의 멀티스레드가 사용되고 있기 떄문이다.
따라서 만약 스레드 개수의 한도를 초과할 경우 스레드가 비워질때 까지 기다렸다가 다음 명령이 실행된다.
function delay() {
for (var i = 0; i < 100000; i++);
}
function foo() {
delay();
bar();
console.log("foo!");
}
function bar() {
delay();
console.log("bar!");
}
function baz() {
console.log("baz!");
}
setTimeout(baz, 10);
foo();
콘솔창 결과는 bar! -> foo! -> baz! 의 순서로 나타난다.
자바스크립트엔진은 기본적으로 싱글스레드이다.
그러나 비동기처리가 이루어질 경우 멀티스레드를 사용한다는 점에서
동기와 비동기, 싱글스레드와 멀티스레드의 타이밍이 존재하는 방식으로 동작한다.