이번에는 이벤트루프에 대해 써보려고한다.
목차
1. 개요
2. 자바스크립트 엔진
3. 런타임환경
3-1. Web APIs
3-2. 콜백 큐
4. 이벤트루프
5. 태스크큐와 마이크로태스크큐
6. 정리
” 자바스크립트는 싱글 쓰레드 기반이며 논 블로킹 방식의 비동기적인 동시성 언어이며 콜 스택, 이벤트 루프와 콜백 큐 그리고 여러가지 다른 API들을 가지고 있다. ”
자바스크립트의 특징 중 하나는 싱글 스레드 언어라는 것이다. 이는 콜스택이 하나이고 따라서 한 번에 하나의 태스크만 처리할 수 있다는 뜻이다. 하지만 막상 브라우저가 동작하는 것을 살펴보면 많은 태스크가 동시에 처리되는 것처럼 보인다.
이는 비동기로 동작하기 때문에 단일 스레드임에도 불구하고 동시에 많은 작업을 수행할 수 있는 것은 이벤트루프 덕분이다. 자바스크립트는 이벤트 루프를 이용해서 비동기 방식으로 동시성을 지원한다.
자바스크립트 엔진은 JS코드를 이해하고 실행하는 것을 도와준다. 가장 유명한 엔진으로는 구글의 V8이 있다.
자바스크립트 엔진은 크게 메모리힙과 콜스택으로 이루어져 있다.
메모리 할당이 일어나는 장소
코드가 실행될 경우 하나씩 stack의 형태로 쌓이는 장소
여기서 중요한 사실은 자바스크립트에는 이벤트 루프가 없다는 것이다. V8과 같은 자바스크립트 엔진은 단일 호출 스택을 사용하고, 요청이 들어오면 그 순서에 따라 순차적으로 처리할 뿐입니다.
비동기 요청은 자바스크립트 엔진을 구동하는 런타임 환경, 즉 브라우저나 Node.js가 담당한다. 런타임 환경에서는 Web API와 Event Loop를 제공한다.
이렇게 자바스크립트 엔진과 런타임환경이 만나 앞서 나왔던 익숙한 그림이 완성된다.
그림을 보면 Web APIs는 자바스크립트 엔진 밖에 그려져있는 것을 볼 수 있다. 즉, Web APIs는 자바스크립트 엔진이 아니고 브라우저에서 제공하는 API이다. DOM, Ajax, TimeOut 등이 있다. 콜스택에서 실행된 비동기 함수들은 모두 Web API를 호출한다. 그리고 Web API는 비동기 함수의 콜백 함수를 콜백 큐
(Callback Queue)에 넣는다.
비동기적으로 실행된 콜백 함수가 보관되는 곳
이다. 쉽게 말하면 콜 스택에 가기 위한 대기열이라고 할 수 있다.
콜백큐에는 세가지 종류가 있다. 태스크 큐,마이크로태스크 큐,애니메이션 프레임이 그것이다.
- 태스크 큐 : setTimeout, setInterval 등
- 마이크로태스크 큐 : Promise callback, async callback 등
- 애니메이션 프레임 : requestAnimationFrame
이제 남은 것은 이벤트루프 뿐이다. 자바스크립트 엔진과 그 런타임 환경을 상호 연동 시켜주는 장치가 바로 이벤트 루프이다. 이벤트루프의 원리는 간단하다.
콜 스택과 콜백 큐를 감시하다가 콜 스택에 쌓여있는 함수가 없는 경우, 콜백 큐에 있는 콜백함수를 콜 스택으로 넘긴다.
여기서 큐마다 우선순위가 다르고 우선순위에 따라 순차적으로 스택으로 옮겨진다. 우선순위는 마이크로태스크 큐 > 애니메이션 프레임 > 태스크 큐
순으로 우선순위가 높은 큐가 비워져야만 다음 큐가 실행된다.
여기서 태스크 큐와 마이크로태스크 큐를 자세히 알아보면 앞서 말했듯이 마이크로 태스크 큐의 콜백함수가 우선순위를 가지고 때문에 마이크로태스크 큐의 콜백 함수를 전부 실행하고나서 태스크 큐의 콜백 함수들을 실행한다.
setTimeout()
, setInterval()
, UI 렌더링, script
, mousemove
마이크로태스크 큐
Promise
(.then/catch/finally), MutationObserver
, async/await
여기서 면접 단골 질문인
setTimeOut()
과Promise
객체의 함수 중 어떤 것이 먼저 실행될까요? 하는 질문들이 나오는 것이다.이벤트 루프는 콜 스택을 감시하고 있다가 콜 스택이 비면 콜백 큐에 있는 콜백 함수를 스택으로 넘긴다. 이 때 마이크로 태스크큐가 태스크큐 보다 먼저 실행되기 때문에 Promise 객체의 함수가 먼저 실행되고 setTimeOut() 함수가 실행된다.
console.log('안녕'); setTimeout(() => console.log('큐!'),0); Promise.resolve().then(() => console.log('마이크로 큐!'));
의 코드에서 결과는
안녕 마이크로 큐! 큐!
가 되는 것이다.
이벤트 루프의 원리는 간단한데 이를 설명하기 위해서는 자바스크립트 엔진과 실행환경에 대한 이해가 필요하다. 때문에 면접 단골 질문이 된 듯하다.
- 코드가 실행되면 콜 스택(Call Stack)에 쌓이고 선입후출 방식대로 실행된다.
- 이 때 비동기 함수가 실행되면 Web API가 호출된다. Web API는 비동기함수의 콜백함수를 콜백 큐(Callback Queue)에 밀어넣는다
- 각각 함수에 따라 맞는 큐로 (setTimeOut 은 태스크 큐, Promise 는 마이크로태스크 큐)
- 이벤트루프(Event Loop)는 콜 스택과 콜백 큐를 계속 감시하고 있다가 콜 스택이 빈 상태가 되면 큐에 있는 콜백을 콜 스택으로 이동시킨다
- 큐의 우선 순위는
마이크로태스크 큐 > 애니메이션 프레임 > 태스크 큐
이다.
How JavaScript works: an overview of the engine, the runtime, and the call stack
Tistory | [JS] 도대체 이벤트 루프가 뭔가요?
Tistory | JavaScript 비동기 핵심 Event Loop 정리