이벤트 루프(Event Loop)

지인혁·2023년 9월 24일
0
post-custom-banner

자바스크립트를 사용하면 가끔씩 코드가 내가 생각했던 흐름대로 흐르지 않고 뒤죽박죽으로 실행되는 경우가 있다.

이는 자바스크립트의 이벤트 루프라는 개념때문이다. 이벤트 루프를 이해하지 못하는 것은 자바스크립트를 다루기 힘들정도로 중요한 개념이며 이벤트 루프에 대해 살펴보자.


자바스크립트는 싱글 스레드

자바스크립트는 단일(싱글) 스레드 언어이다. 이 말은 한 번에 하나의 작업만 처리할 수 있다는 뜻이다.

자바스크립트를 이용하다보면 여러가지 이벤트와 동시에 처리해아하는 경우가 많다. 동시성 문제를 해결하기 위해 자바스크립트는 이벤트 루프와 함께 비동기 프로그래밍 모델을 사용한다.

자바스크립트 내부 구조

먼저 JS라고 적힌 네모 부분이 자바스크립트 코드를 실행하는 엔진 부분이며 오른쪽 그림 중 Web APIs는 JS가 아닌 멀티스레드의 브라우저, Node.js 환경에서 처리하는 비동기 함수다. 이러한 환경에서 이벤트 루프가 어떻게 동작하는지 살펴보자

Heap : 힙은 메모리가 할당되는 곳이며 선언한 변수, 함수의 값이나 객체 참조 값이 담겨져 있다.

Call Stack : 함수 호출 시 이곳에 저장되며 스택의 특성 LIFO(Last In First Out)의 특성을 지니고 있다.

함수를 여러번 호출하면 맨 마지막에 있는 함수를 하나씩 처리해 나간다. JS는 단일 스레드로서 반드시 한 번에 하나의 작업을 수행한다.

Web APIs : Web API는 JS가 아닌 브라우저에서 제공하는 API로 DOM, Ajax, TimeOut의 비동기 함수가 있다. Call Stack에서 Web APIs 함수들이 있으면 Call Stack이 아닌 Web APIs 영역에서 처리를 한다.

이때 Web APIs는 JS가 아닌 멀티 스레드의 브라우저에서 실행하기에 JS의 단일 스레드 구조와 별개로 동작한다. 이 말은 브라우저의 Web APIs 비동기 함수가 처리되면서 동시에 JS의 Call Stack도 다른 작업을 계속해서 처리할 수 있다는 뜻이다.

Callback Queue : 콜백 큐에서는 Web APIs의 비동기 처리가 끝난 함수의 콜백 함수를 순서대로 저장한다.

큐의 특성상 FIFO(First In First Out) 먼저 들어온 콜백 함수를 제일 먼저 처리한다.

Event Loop : 이벤트 루트는 만약 JS의 Call Stack이 비어있다면 그제서야 Callback Queue에 존재하는 콜백 함수를 순서대로 Call Stack에 옮겨 작업을 처리하는 역할을 한다.

Callback Queue의 함수들은 바로 실행되지 않는다. 기다렸다가 반드시 Call Stack이 비어있을때에 Callback Queue 함수들이 순서대로 실행된다.

Call Stack에서 오래걸리는 작업을 하게된다면 Callback Queue의 함수들이 계속해서 기다리게 된다. 그래서 JS를 이용한다면 시간이 오래걸리는 연산은 피하는게 좋다.

이벤트 루프 동작 과정

function main(){
    console.log('1');

    setTimeout(() => { 
        console.log('2'); 
    }, 0);

    console.log('3');
}
main();

이 코드를 만약 Node.js 환경에서 실행하면 실행 순서가 어떻게 될까?

  • 자바스크립트를 실행하면 main 함수가 호출이 되어 Call Stack에 추가된다.
Call StackWeb APIsCallback Queue
main()

  • main 함수의 첫 코드 console.log('1')을 호출하며 Call Stack에 추가한다.
Call StackWeb APIsCallback Queue
console.log('1')
main()

  • console.log('1')이 실행되어 1을 출력하고 Call Stack에서 Pop 된다.
Call StackWeb APIsCallback Queue
main()

  • 다음 코드인 setTimeout이 Call Stack에 추가 되지만 브라우저가 Web API를 호출하며 Call Stack에서 제거 된다.
Call StackWeb APIsCallback Queue
setTimeout(() => { console.log('2'); }, 0);
main()

Call StackWeb APIsCallback Queue
main()setTimeout(() => { console.log('2');

  • 브라우저에서 setTimeout의 비동기 함수가 실행되고 있으며 다음 코드인 console.log('3')가 Call Stack에 추가 된다.
Call StackWeb APIsCallback Queue
console.log('3')
main()setTimeout(() => { console.log('2'); }, 0);

  • console.log('3')이 실행되어 3을 출력하고 Call Stack에서 Pop 된다. 그리고 Web APIs의 setTimeout 함수도 끝나 콜백 함수인 console.log('2)가 Callback Queue에 저장된다.
Call StackWeb APIsCallback Queue
main()console.log('2')

  • main() 함수가 이제 종료 되어 Call Stack에서 Pop 된다.
Call StackWeb APIsCallback Queue
console.log('2')

  • Call Stack이 이제 비어있으므로 이벤트 루프가 동작하여 Callback Queue를 하나씩 꺼내어Call Stack으로 추가한다.
Call StackWeb APIsCallback Queue
console.log('2')

  • console.log('2')이 Call Stack에서 실행되어 3을 출력하고 Call Stack에서 Pop된다.
Call StackWeb APIsCallback Queue

  • 1출력 -> 3출력 -> 2출력의 과정을 거치며 이러한 동작을 이벤트 루프라고 한다.

Queue의 종류

큐의 종류는 Task Queue, MicroTask Queue, Animation Frames 총 3가지가 있다. 이벤트 루프에서 여러 Queue들에 우선순위를 부여해 어떤 Queue를 먼저 수행할지 결정한다.

Task Queue(Macrotask Queue) : Task Queue는 Macrotask Queue라고도 불리며 setTimeout(), setInterval(), setImmediate()와 같은 작업을 넘겨받는다.

Microtask Queue : Microtask Queue는 Promise, async/await, process.nextTick, Object.observe, MutationObserver과 같은 비동기 호출을 넘겨받는다. 이는 Task Queue보다 우선순위가 높으며 먼저 콜 스택에서 처리된다.

Animation Frames : Animation Frames는 requestAnimationFrame과 같이 브라우저 렌더링과 관련된 작업을 넘겨받는다. 우선순위는 Microtask보다는 낮고 Task Queue보다는 높다.

정리하자면 3가지 종류의 큐가 있고 각 큐마다 넘겨받는 작업의 종류가 다르다. 각 큐는 우선순위를 가지며 순위가 높은 순서대로 콜 스택에서 처리된다.

Microtask Queue > Animation Frames > Task Queue

profile
대구 사나이
post-custom-banner

0개의 댓글