자바스크립트는 단일 스레드
이다. 이 말의 뜻은 한 번에 1개의 작업만 할 수 있다는 뜻이다.
메모리 힙(Memory Heap)
은 정보를 저장하는 공간이고 콜스택(Call Stack)
은 실행 중인 코드를 추적하는 공간이다.
자바스크립트는 힙, 큐와 함께 구성하는 단일 콜스택을 갖는다. 메모리 힙은 자바스크립트 엔진이 구동되면서 변수, 함수 저장, 호출 등의 작업이 발생하는 공간이다.
콜스택은 코드를 읽어내려가며 수행 할 작업들을 밑에서 부터 하나씩 쌓고, 메모리 힙에서 필요한 것들을 찾아서 작업을 수행하는 공간이다. 이름에서 알 수 있듯이 스택 자료구조를 가지고있으며 LIFO(Last In First Out)
또는 FILO(First In Last Out)
형태이다. 즉, 먼저 들어온 것이 나중에 나가는 형태이다.
간단한 예제를 통해 더 자세히 알아보자.
function first () {
console.log("first");
}
function second () {
first();
console.log("second");
}
function third () {
second();
console.log("third");
}
third();
위의 코드가 실질적으로 콜스택에 쌓이고 나가는 과정은 아래와 같다.
상자에 블럭을 하나씩 넣고 그 것을 차례대로 뺀다고 생각하면 이해하기 쉽다.
또한, 자바스크립트 엔진에 위치하는 것이 아니라 실행환경인 브라우저에 위치한다.
태스크 큐(Task Queue)
는 생성된 태스크가 적재되는 큐이다. 태스크는 비동기 함수를 실행시켰을 때 콜스택에 바로 push 되지 않고 Web API(Window, Document, Event, XMLHttpRequest, Fetch 등)와 같은 백그라운드로 작업을 넘겨준다.
예를 들어, setTimeout()
을 실행하면 백그라운드로 넘기고 지정된 시간만큼 실행시킨다. 그리고 그 안에 있는 콜백함수를 태스크 큐로 넘겨준다. 후에 콜스택이 모두 실행된 후에 태스크 큐에 있던 콜백 함수가 콜스택으로 push되어 실행된다.
여기서 태스크 큐에 있던 작업이 스스로 올라가는 것이 아니라 이벤트 루프(Event Loop)
에 의해 올라간다. 이벤트 루프는 콜스택이 텅 빌 때 까지 기다리다가 빈 것을 확인하면 태스크 큐를 콜스택으로 올려준다.
그래서 콜스택에 쌓인 작업이 모두 끝나기 전에는 태스크 큐에 있던 작업들은 실행되지 않는다.
콜스택과 태스크 큐, 이벤트 루프의 관계를 정리해보자.
- 비어있는 태스크 큐에 1개의 태스크가 들어오고, 이벤트 루프에 의해 태스크가 큐에서 pop된다.
- pop된 태스크가 실행되면 자바스크립트 엔진에 의해 실행 가능한 코드범위로 실행 컨텍스트가 생성된다.
- 이벤트 루프가 태스크 큐에서 pop된 작업을 콜스택에 push한다.
- 콜스택에서 작업이 실행 되고, 콜스택에서 pop된다.
- 이벤트 루프가 태스크 큐에 작업이 있는지 확인한다.
- 이 과정을 반복한다.
이 말을 풀어서 간단하게 하면, 콜스택에 쌓인 태스크들이 모두 실행이 되고, 종료가 된 시점에 태스크 큐에 쌓여있던 콜백함수(setTimeout())가 이벤트 루프에 의해 콜스택으로 push가 되어서 실행이 된다.
콜스택에 작업이 끝나지 않으면 절대 태스크 큐에 있는 작업들이 실행되지 않는다고 언급했다. 여기서 궁금증이 생겼다. 예를 들어, 콜스택에 1초가 걸리는 작업("test"를 1만번 출력하고 1초의 시간이 필요함)이 쌓여있다. 그런데 setTimeout(lalala, 0.5)
을 실행하면 0.5초 후에 test의 결과("lalala")가 나와야한다. 여기서 단순하게 추측을 해보면 당연히 test가 출력되다가 0.5초 후에 test가 나오고 나머지 test들이 나와야한다.
0.5초 후에 실행되어야 하니 콜스택에 작업이 남아있더라도 실행이 될까요?
test() x 5000번 출력
lalala 출력
test() x 5000번 출력
0.5초 후에 실행되어야 하지만 콜스택에 작업이 남아있기 때문에 무시하고 실행되지않을까요?
test() x 10000번 출력
lalala() 출력
결과는 두 번째의 경우가 맞다. 아무리 0.5초후에 실행이 되어야 해도 콜스택에 있는 작업들이 끝나기 전에 태스크 큐에 있는 작업은 실행되지 않는다. 그래서 setTimeout()
의 시간이 정확하지 않을 수도 있고 많은 양의 작업이 콜스택에 담겨있으면 좋지 않다. 너무 단순한 예시일 수도 있지만 결국 콜스택에 작업이 모두 끝나지 않으면 태스크 큐에 있는 작업이 실행되지 않는다는 것이다.