들어가며
자바스크립트의 특징은 single thread 기반언어이며 즉, 자바스크립트는 한개의 호출 스택을 가진다. 그렇기 때문에 한 번에 하나의 일만 처리 할 수 있는데 신기하게도 웹브라우저에서는 애니메이션 효과를 보여주며 마우스 입력을 받아서 처리하고, Node.js기반의 웹서버에서는 동시에 여러 개의 HTTP 요청을 처리한다. 자바스크립트는 무엇때문에 현재 작업 완료를 기다리지 않고 다음 작업을 수행하며 동시성(Concurrency)을 지원할까? 답은 이벤트 루프이다. 자바스크립트는 이벤트 루프 기반의 비동기 방식으로 Non-Blocking IO를 지원한다.
📌 ECMAScript에는 이벤트 루프가 없다
자바스크립트는 비동기로 동작하기 때문에 single thread임에도 불구하고 동시에 많은 작업을 할 수 있다. 하지만 자바스크립트는 요청이 들어올 때 마다 호출 스택에 해당 요청을 담아 처리 할 뿐이고 실제 비동기 작업과 동시성 처리는 자바스크립트 엔진을 구동하는 런타임 환경 브라우저 또는 Node.js가 담당한다. 브라우저에서는 비동기 호출을 위해 사용하는 setTimeout이나 XMLHttpRequest와 같은 함수들은 자바스크립트 엔진이 아닌 Web API 영역에 따로 정의되어 있다. 그리고 Node.js는 비동기 IO를 지원하기 위해 libuv 라이브러리를 사용하며, 이 libuv가 이벤트 루프를 제공한다. 자바스크립트 엔진은 비동기 작업을 위해 Node.js의 API를 호출하며, 이때 넘겨진 콜백은 libuv의 이벤트 루프를 통해 스케쥴되고 실행된다.
📌 자바스크립트 엔진
대표적인 자바스크립트 엔진으로 google V8 engine이 있다.
Memory Heap : 메모리 할당(변수, 함수의 할당)이 일어난다.
Call Stack : 코드 실행에 따라 Call Stack이 쌓인다. 후입선출 방식(LIFO)
📌 자바스크립트 런타임
자바스크립트 엔진 이외에도 자바스크립트에 관여하는 런타임 구성요소로 Wep API, Task Queue, Event Loop등이 있다.
📌 호출 스택(Call Stack)
위에서 언급한데로 자바스크립트는 싱글 스레드 언어로 단일 호출 스택이다. 스택 자료구조이기 때문에 스택에 쌓인 함수나 코드를 위에서 아래로 차례대로 실행한다. 하나의 작업이 끝나면 pop하고 아래의 함수, 코드를 차례대로 실행 하므로 하나의 작업이 끝날때까지 다음 작업을 실행하지 않는다.
코드 예시
function foo() {
bar();
console.log("foo!");
}
function bar() {
baz();
console.log("bar!");
}
function baz() {
console.log("baz!");
}
foo();
// 결과순서 1) baz! 2) bar! 3) foo!
호출 순서 foo() → bar() → baz()
main함수는 처음 실행 시 전역 컨텍스트(함수가 호출되었을 때 생성되는 환경)이다.
baz() → bar() → foo() → main() 순으로 실행되어 하나씩 pop되고 main함수 실행이 끝나면 호출 스택이 비워진다.
위 코드의 실행 결과는 1) baz! 2) bar! 3) foo!로 console에 찍힌다.
📌 Web APIs
Web APIs는 브라우저에서 제공하는 API로 setTimeout, DOM 이벤트, HTTP request(AJAX) method 등을 제공한다.
📌 Callback Queue
이벤트 발생 후 호출되어야 할 콜백 함수들이 기다리는 공간. 이벤트 루프가 정한 순서대로 줄을 서 있으므로 콜백 큐(Callback Queue) 라고 한다.
📌 Event Loop
이벤트 발생 시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정한다.
1. Call Stack과 Callback Queue를 감시
2. Call Stack이 비어있을 경우, Callback queue에서 함수를 꺼내 Call Stack에 추가
그러면 Event loop 예시를 보며 아래의 코드가 어떻게 비동기로 작동하는지 자세히 알아보자
console.log("start");
setTimeout(function(){
console.log("1초 후 실행");
}, 1000);
console.log("end");
// 결과
// 1) start 2) end 3) 1초후 실행
1. 전역 환경에서 실행되는 main함수가 Call stack에 추가되고 이어서 console.log("start")가 추가된다.
2. Browser console에 "start"가 출력되고 Call stack에서 console.log("start")가 제거된다.
3. Call stack에 setTimeout함수가 추가된 후 setTimeout함수가 실행되면서 Browser가 제공하는 Web APIs를 호출하고 callback 함수를 전달한다.
4. Call Stack에서 setTimeout함수가 제거되고 console.log("end")가 추가된다.
5. Browser console에 "end"가 출력되고 Call stack에서 console.log("end")가 제거된다.
6. 더 이상 실행될 함수나 코드가 없어 main함수가 제거되고 setTimeout 함수에 전달한 1000ms 시간이 지난 뒤 Callback Queue에 callback 함수가 추가된다.
7. Event Loop는 Call Stack이 비어있는 것을 확인하고 Callback Queue에서 함수를 꺼내 Call stack에 추가한다.
8. callback 함수가 실행되고 내부의 console.log("1초 후 실행")이 Call Stack에 추가된다.
9. Browser console에 "1초 후 실행"이 출력되고 Call Stack에서 제거된다. Event Loop는 Call Stack과 Callback Queue를 계속 감시한다.
📌 참고
https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/
'프리온보딩' 카테고리의 다른 글
[POB] 세션정리 - 1주차 목 (1) | 2021.09.05 |
---|---|
[POB] 기업과제 - 원티드 #4 (2) | 2021.08.17 |
브라우저 동작 원리 알아보기 (2) | 2021.08.13 |
[POB] 기업과제 - 자란다 #3 (1) | 2021.08.07 |
[POB] 기업과제 -미스터카멜 #2 (3) | 2021.08.01 |