지난 포스트에서 잠깐 다루었던 콜 스택(call stack)에 대해 알아보고,
조금 더 나아가 자바스크립트는 한 순간 하나의 작업만 처리할 수 있는 단일 스레드 기반의 언어임에도 불구하고 동시에 많은 작업을 수행할 수 있는지 알아보겠습니다.
스택과 큐
콜 스택에 알아보기 전 스택과 큐 자료구조에 대해 간단히 알아보고 넘어가겠습니다.
스택은 출입구가 하나인 데이터 구조입니다. 순서대로 a, b, c 데이터를 넣었다면 꺼낼때는 반대로 c, b, a순서로 꺼내게 됩니다. (FILO, First In Last Out)
큐는 양쪽이 열려있는 파이프를 떠올리면 됩니다. 종류에 따라 양쪽 모두 입/출력이 가능한 큐도 있으나 일반적으로 큐에 순서대로 a, b, c 데이터를 넣었다면 꺼낼때는 a, b, c 순서대로 꺼내게 됩니다. (FIFO, First In First Out)
콜 스택(Call Stack)이란?
콜 스택이란, 자바스크립트 코드가 실행되며 생성되는 실행 컨텍스트(Execution Context)를 저장하는 자료구조라 정의 할 수 있습니다.
1. 함수를 호출하면 실행 컨텍스트가 생성되고, 이를 콜 스택에 추가한 다음 함수를 수행하기 시작합니다.
2. 함수에 의해 호출되는 모든 함수(내부 함수들)는 콜 스택에 추가되고 해당 위치에서 실행합니다.
3. 함수의 실행이 종료되면 해당 실행 컨텍스트를 콜 스택에서 제거한 후 중단 된 시점부터 다시 시작합니다.
4. 만약 스택이 할당 된 공간보다 많은 공간을 차지하면 'stack overflow'에러가 발생합니다.
function foo() {
console.log("foo");
}
function bar() {
console.log("bar");
}
function baz() {
foo();
console.log("baz");
bar();
}
baz();
위 예제 코드는 다음과 같이 실행 될 것입니다.
1. baz 함수를 호출할 때까지 모든 함수를 무시합니다.
2. baz 함수를 호출합니다.
3. baz 함수를 호출하여 생성 된 실행 컨텍스트를 콜 스택에 추가합니다.
4. baz 함수 내의 모든 코드를 읽기 시작합니다.
5. foo 함수를 호출합니다.
6. foo 함수를 호출하여 생성 된 실행 컨텍스트를 콜 스택에 추가합니다.
7. foo 함수 내의 모든 코드를 읽기 시작합니다.
8. foo 함수 내의 모든 코드를 읽었다면 해당 실행 컨텍스트를 제거합니다.
9. foo 함수가 호출 된 라인으로 돌아와 나머지를 계속 실행합니다.
10. bar 함수도 5~9과 마찬가지로 실행됩니다.
11. baz 함수 내의 모든 코드를 모두 읽었으므로 baz함수가 콜 스택에서 제거됩니다.
결과는 foo, baz, bar 순서대로 출력 될 것입니다.
이처럼 자바스크립트는 한 순간 하나의 작업만을 처리하며 동작하게 됩니다.
만약, 어떠한 함수의 실행이 오래걸린다면 어떨까요?
실행이 되는동안 브라우저가 멈춘 상태로 있을겁니다. 멈춘동안 사용자가 이것 저것 트리거를 발생시킨다면,
오래 걸리던 함수가 종료되는 순간 콜 스택에 주르륵 쌓이고 처리하게 될 것입니다.
혹은 아래 이미지와 같은 창이 떠버릴 수도 있습니다.
즉, 좋은 사용성은 아니란 것입니다.
하지만 우리는 브라우저에서 동시에 많은 일이 일어나는 것을 알고 있습니다.
서버에 데이터를 요청하는 동안 사용자가 인터랙션을 한다던지,
에니메이션이 일어나는 동안 입력을 받아 처리하고,
동시에 http 요청을 보낼 수도 있습니다.
어떻게 동시에 많은 일이 일어나는 것일까요?
비밀은 이벤트 루프에 있습니다.
자바스크립트는 (브라우저 환경에서) 이벤트 루프에 기반한 동시성 모델을 가지고 있습니다.
이것이 단일 스레드임에도 불구하고 브라우저 환경에서는 브라우저의 구성요소와 함께 동시에 많은 작업을 수행할 수 있게 해줍니다.
브라우저 환경에서의 자바스크립트
대체적으로 모던 자바스크립트 엔진들은 아래와 같이 묘사 된 개념을 구현하고 최적화 하게 됩니다.
앞서 알아본 콜 스택외에 생소한 것들이 보입니다. 하나씩 알아볼까요?
Heap
힙은 구조화되지 않은 넓은 메모리 영역을 지칭합니다.
이전에 알아본 자바스크립트의 Reference Type 즉, 객체는 모두 Heap안에 할당됩니다.
Web API
브라우저에서 제공하는 별도의 API 입니다.
프론트엔드 개발을 하며 주로 사용하는 DOM, SVG, Fetch, Canvas, setTimeOut등은 모두 자바스크립트가 아닌 브라우저에서 제공하는 API입니다.
Callback Queue(Messege Queue)
비동기 함수가 실행 된 후 콜백 함수가 대기하는 자료구조 입니다.
Microtask Queue(Job Queue)
ES6에서 도입 된 새로운 컨셉으로, Callback Queue와 동일 계층에 존재하며 Promise를 통한 비동기 요청 시의 콜백 함수는 Microtask Queue에 대기하게 됩니다.
Animation Frames
requestAnimationFrame에 의해 등록되는 자료구조로 requestAnimationFrame의 콜백 함수가 대기하는 자료구조 입니다.
Event loop(이벤트 루프)
이벤트 루프는 Call Stack과 각 Queue를 감시하고 있다가 Call Stack이 비었을 경우 정해진 우선순위에 따라 queue에서 하나씩 꺼내 Call Stack에 추가해주는 역할을 합니다.
1. 호출 스택의 작업을 모두 처리합니다.
2. 호출스택이 비었을 경우, Microtask Queue를 확인하고 처리해야 할 작업이 있다면 Call Stack에 넣고 처리합니다.
3. 만약 MicroTask Queue가 비었을 경우에는 Animation Frames를 확인하고 마찬가지로 처리해야 할 작업이 있다면 Call Stack에 넣고 처리합니다.
4. 1~3과정을 거치고 난 후 마지막으로 Callback Queue를 확인하고 마찬가지로 Call Stack에 넣고 처리합니다.
Microtask Queue ➡️ Animation Frames ➡️ Callback Queue의 순서로 비동기 함수의 콜백 함수를 처리하게 됩니다.
console.log("start");
setTimeout(function () {
console.log("Timeout");
}, 0);
Promise.resolve().then(function () {
console.log("Promise");
});
requestAnimationFrame(function () {
console.log("rAF");
});
console.log("end");
위 예제 실행시 로그 출력결과는 start → end → Promise → rAF → Timeout 순으로 실행되는 것을 확인 할 수 있습니다.
일부 브라우저에서는 Animation Frames와 Callback Queue를 함께 처리할 수 있습니다.
따라서 브라우저마다 반드시 실행 순서가 보장된 것은 아닙니다.
위 순서는 크롬 브라우저(V8)기준으로 작성되었습니다!
Zero Delay
개발하며 setTimeout을 종종 사용하게 되는데요, 위에서 알아본 것처럼 setTimeout의 두번째 인수로 0을 넘겨주더라도, 0ms 후에 콜백이 시작된다는 의미는 아니라는 것을 알 수 있습니다.
setTimeout 콜백 함수의 실행은 큐에 대기중인 작업 수에 따라 차이가 날 수 있습니다!
따라서 지연시간(delay)는 함수가 해당 딜레이 이후 실행이 보장되는 시간이 아니라 실행하기 위한 최소의 시간입니다.
왜냐하면 setTimeout은 대기중인 모든 작업이 완료된 이후 실행 될테니까요 :)
여기까지 자바스크립트의 아주 중요한 개념중 하나인 이벤트루프에 대해 알아보았습니다.
조금은 어려울 수 있는 주제였지만, 이를 통해 자바스크립트와 더욱 친해질 수 있길 바랍니다 :)
긴 글 읽어주셔서 감사합니다.
References
- https://meetup.toast.com/posts/89
- https://nodejs.dev/learn/the-nodejs-event-loop
- https://developer.mozilla.org/ko/docs/Web/API/setTimeout
'개발 > Javascript' 카테고리의 다른 글
9. JavaScript의 변수(var, let, const의 차이) (0) | 2022.06.09 |
---|---|
7. Javascript의 this와 execution context (0) | 2021.07.20 |
6. Javascript의 strict mode (0) | 2021.07.19 |
5. Javascript의 스코프 (0) | 2021.07.15 |
4. Javascript의 함수 (0) | 2021.07.15 |