본문 바로가기

Language/JavaScript

<이벤트루프> JS와 이벤트루프

1. 소개

* JS 와 이벤트루프

 - 이벤트 루프(event loop)는 태스크가 들어오면 이를 처리하고, 잠들고, 처리하는 일련의 과정을 반복하는 자바스크립트의 루프를 의미한다. 그렇다면 이것이 무슨 의미가 있을까.

 

 - 자바스크립트는 '단일 스레드' 기반의 언어이다. 하지만 자바스크립트를 써본 사람들은 이미 알겠지만 여러 작업이 동시에 처리되고 있음을 알 수 있다. 이러한 작업에 필요한 것이 '이벤트 루프'이다.

 

 - 정리하면 자바스크립트는 태스크를 처리하는 이벤트 루프를 통해 비동기 방식으로 동시 작업을 지원한다.

 

 - 이 때 알아둬야 할 것은 이벤트 루프는 ECMAScript 스펙에 등재된 것이 아닌 브라우저나 노드JS에서 지원하는 것이라는 점이다. 자바스크립트 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청을 순차적으로 처리하기만 한다. 동시성 지원은 이 엔진을 구동하는 환경이 담당한다.

 

* 브라우저

 - 웹 브라우저는 위의 이벤트 루프를 어떻게 구현하고 있을까. 이를 그림으로 확인하면 다음과 같다.

 

https://meetup.toast.com/posts/89

 - 우리가 흔히 웹에서 사용하는 비동기 호출은 웹API영역에 정의 되어 있는 것을 볼 수 있다.

 

 - 그리고 이벤트루프와 태스크 큐도 엔진 외부에 존재하고 있다.

 

 - 웹 사이트에서의 코드는 메인스레드에서 실행되어 위의 이벤트 루프를 공유하게 된다.

 

 

* 노드JS

 - 노드 JS는 이벤트루프를 다음과 같이 구현하고 있다.

 

https://stackoverflow.com/questions/10680601/nodejs-event-loop

 - 비동기로직을 처리하기 위해 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"

 

 - 그렇다면 태스크 큐에 여러 작업이 들어가면 어떻게될까? 이는 다음 포스트에서 알아보자.

 

 

 

 


참고

 

 

자바스크립트와 이벤트 루프 : NHN Cloud Meetup

자바스크립트와 이벤트 루프

meetup.toast.com

 

 

이벤트 루프와 태스크 큐 (마이크로 태스크, 매크로 태스크)

자바스크립트는 싱글 스레드 기반의 언어이고, 자바스크립트 엔진은 하나의 호출 스택만을 사용한다. 이는 요청이 동기적으로 처리되어, 한 번에 한 가지 일만 처리할 수 있음을 의미한다. 만약

velog.io

 

 

Node.js 교과서 개정2판

기본기에 충실한 노드제이에스 14 입문서

www.gilbut.co.kr

 

 

이벤트 루프와 매크로·마이크로태스크

 

ko.javascript.info