1. 소개
* JS 와 이벤트루프
- 이벤트 루프(event loop)는 태스크가 들어오면 이를 처리하고, 잠들고, 처리하는 일련의 과정을 반복하는 자바스크립트의 루프를 의미한다. 그렇다면 이것이 무슨 의미가 있을까.
- 자바스크립트는 '단일 스레드' 기반의 언어이다. 하지만 자바스크립트를 써본 사람들은 이미 알겠지만 여러 작업이 동시에 처리되고 있음을 알 수 있다. 이러한 작업에 필요한 것이 '이벤트 루프'이다.
- 정리하면 자바스크립트는 태스크를 처리하는 이벤트 루프를 통해 비동기 방식으로 동시 작업을 지원한다.
- 이 때 알아둬야 할 것은 이벤트 루프는 ECMAScript 스펙에 등재된 것이 아닌 브라우저나 노드JS에서 지원하는 것이라는 점이다. 자바스크립트 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청을 순차적으로 처리하기만 한다. 동시성 지원은 이 엔진을 구동하는 환경이 담당한다.
* 브라우저
- 웹 브라우저는 위의 이벤트 루프를 어떻게 구현하고 있을까. 이를 그림으로 확인하면 다음과 같다.
- 우리가 흔히 웹에서 사용하는 비동기 호출은 웹API영역에 정의 되어 있는 것을 볼 수 있다.
- 그리고 이벤트루프와 태스크 큐도 엔진 외부에 존재하고 있다.
- 웹 사이트에서의 코드는 메인스레드에서 실행되어 위의 이벤트 루프를 공유하게 된다.
* 노드JS
- 노드 JS는 이벤트루프를 다음과 같이 구현하고 있다.
- 비동기로직을 처리하기 위해 libuv 라이브러리를 사용하여 이벤트루프를 제공하고 있다.
- 노드JS의 비동기작업으로 넘겨진 콜백은 위의 이벤트 루프에 들어가서 스케쥴링되어 처리된다.
* 요약
- 위의 두 환경의 이벤트 루프를 들여다 보면 상당히 비슷함을 알 수있다. 이를 간단하게 추상화해보면 다음과 같이 그릴 수 있다.
- 기본적으로 콜스택에 작업이 쌓이고 비동기 작업은 백그라운드(각 API가 있는 브라우저 또는 os)와 태스크큐에서 처리된다.
- 위에서 알 수 있듯이 자바스크립트는 단일 호출 스택을 사용한다는 관점에서는 단일 스레드 기반의 언어가 맞지만, 실제 자바스크립트가 구동되는 환경에서는 여러 개의 스레드가 사용되고 있다. 이러한 환경의 연동이 '이벤트 루프'를 통해 이루어지는 것이다.
- 서론이 길었으니 이제 실제로 돌아가는 모습을 코드와 이미지로 확인해보자.
2. 실행
* 콜 스택
- 먼저 콜스택만을 사용하는 예제를 보자.
function myFunc1() {
myFunc2();
console.log("bye");
}
function myFunc2() {
console.log("hello");
}
myFunc1();
// hello
// bye
- 위는 myFunc1 함수가 myFunc2 함수를 호출하고 있다. 위의 실행 순서는 쉽게 예상할 수 있듯이 "hello" -> "bye" 이다.
- 이를 이미지로 확인해보면 다음과 같다.
- 전역 환경에서 실행되는 코드는 가상의 익명함수로 감싸져 있다고 볼 수 있다.
- 실행하는 함수들은 차례로 스택에 추가되었다가 제거되는 것을 볼 수 있다. 마지막으로 myFunc1이 실행을 마치고, 완전히 코드가 종료된다.
* 비동기
- 이제 위의 코드에서 비동기 작업을 추가해보자.
function myFunc1() {
setTimeout(() => {
console.log("time");
}, 0);
myFunc2();
console.log("bye");
}
function myFunc2() {
console.log("hello");
}
myFunc1();
- 위의 코드는 콘솔이 어떤식으로 찍힐 것 같은가.
- 위에서 봤던 함수와 다른점은 myFunc2를 호출하기전에 0초 딜레이를 가진 익명함수를 실행시키는 것이다.
- 이미 기존에 자바스크립트를 꽤나 접해본 사람이라면 예상했듯이 "hello" -> "bye" -> "time" 순으로 실행된다.
- 비동기 작업이 딜레이가 없음에도 마지막에 실행되는데, 왜 그런 것인지 이벤트루프 이미지를 보면서 확인해보자.
- setTimeout 으로 불린 함수는 타이머 이벤트를 요청하자마자 스택에서 제거된다.
- 브라우저나 노드가 시간을 확인하고 태스크 큐라는 작업대기목록으로 위 함수를 보낸다.
- 호출스택이 완전히 비워지면, 비로소 태스크 큐에서 대기중인 작업을 순차적으로 실행한다.
function myFunc1() {
Promise.resolve().then(() => {
console.log("promise");
});
myFunc2();
console.log("bye");
}
function myFunc2() {
console.log("hello");
}
myFunc1();
- 위는 0초 딜레이 함수를 promise로 바꾼 예시이다.
- 당연히 프라미스도 비동기이므로 위와 동일한 결과를 보여준다. "hello" -> "bye" -> "promise"
- 그렇다면 태스크 큐에 여러 작업이 들어가면 어떻게될까? 이는 다음 포스트에서 알아보자.
참고
'Language > JavaScript' 카테고리의 다른 글
<ES9> ES2018 Features (0) | 2021.10.10 |
---|---|
<이벤트루프> 태스크 큐 : 마이크로태스크 큐 & 매크로태스크 큐 (0) | 2021.10.04 |
<ES8> ES2017 Features (0) | 2021.09.26 |
<ES7> ES2016(ES7) Features (0) | 2021.08.07 |
<ES6> ES2015(ES6) Features (2) (0) | 2021.08.01 |