개발 공부 일지/JavaScript

[JS] 이벤트 루프(Event Loop)란?

dev-hpk 2024. 12. 20. 12:17

자바스크립트는 단일 스레드 기반 언어로 한 번에 하나의 작업만 처리할 수 있습니다. 하지만 비동기 작업을 지원하며 동시에 여러 작업이 진행되는 것처럼 보이게 합니다. 이러한 비동기 처리를 가능하게 하는 핵심 메커니즘이 바로 이벤트 루프(Event Loop)입니다.

 

 

목차

1. 싱글 스레드란?

2. 블로킹 & 논블로킹

3. 이벤트 루프의 필요성

4. 마이크로 태스크와 매크로 태스크

추천글

위의 목차를 클릭하면 해당 글로 자동 이동 합니다.

 

1. 싱글 스레드란?

스레드는 프로세스의 실행 단위입니다. 싱글 스레드라는 것은 말 그대로 스레드가 하나만 존재해 한번에 하나의 프로세스만 실행할 수 있다는 의미입니다.

자바스크립트는 싱글 스레드 언어로 하나의 호출 스택(Call Stack)을 사용해 코드를 실행합니다.

 호출 스택
: 함수의 호출을 관리하는 구조로, LIFO(Last In First Out) 방식으로 동작합니다.

 

호출 스택(Call Stack) 예시

function foo() {
  console.log('foo');
}

function bar() {
  foo();
  console.log('bar');
}

bar();
  1. bar() 함수가 호출되어 호출 스택에 추가됩니다.
  2. bar() 내부에서 foo()가 호출되어 foo()가 호출 스택에 추가됩니다.
  3. foo()의 실행이 종료되면 스택에서 제거되고 bar()의 나머지 코드인 console.log('bar')가 실행됩니다.
  4. bar()의 실행이 종료되면 스택에서 제거되고 호출 스택이 비워집니다.

2. 블로킹(Blocking) & 논블로킹(Non-Blocking)

자바스크립트는 싱글 스레드 언어로 호출 스택의 작업을 하나씩 순차적으로 진행한다고 했습니다.

만약 호출 스택에 매우 오래 걸리는 작업이 들어가면 어떻게 될까요🤔

호출 스택에 있는 모든 작업들이 멈춰있다가 해당 작업이 끝난 후에 동작할 것입니다. 이런 경우를 블로킹이라고 합니다.

하지만 우리는 웹에서 작업의 완료 여부와 상관없이 여러 작업을 동시에 실행할 수 있습니다. 이런 경우를 논블로킹이라고 합니다.

블로킹 & 논블로킹 (출처: PoiemaWeb)

 

자바스크립트는 싱글 스레드 언어로 한 번에 하나의 작업만 실행할 수 있지만, 비동기 처리를 통해 기다림 없이 여러 작업을 블로킹 없이 처리할 수 있습니다.

3. 이벤트 루프의 필요성

싱글 스레드 환경에서 비동기 작업(타이머, 네트워크 요청)은 어떻게 처리될까요🤔

여기서 이벤트 루프가 등장합니다. 비동기 작업은 브라우저 또는 Node.js 환경의 백그라운드(Background)에서 처리되고, 완료된 작업은 태스크 큐(Task Queue)에 전달됩니다. 이벤트 루프는 호출 스택이 비어 있는지 확인하고, 비어 있다면 태스크 큐의 작업을 호출 스택으로 이동시킵니다.

  • 콜 스택 : 현재 실행 중인 작업들이 쌓이는 곳
  • 태스크 큐 : 비동기 작업이 완료되면 그 결과를 대기시키는 곳

이벤트 루프의 동작 원리

  1. 호출 스택(Call Stack) : 현재 실행 중인 함수의 실행 컨텍스트를 관리합니다.
  2. 백그라운드 : 비동기 작업(타이머, 네트워크 요청)을 처리합니다.
  3. 태스크 큐(Task Queue) : 백그라운드에서 완료된 작업의 콜백 함수가 대기하는 공간입니다.
  4. 이벤트 루프(Event Loop) : 호출 스택이 비어 있는지 확인하고, 태스크 큐에서 작업을 호출 스택으로 옮깁니다.

예시

console.log('Start');

setTimeout(() => {
  console.log('Timeout');
}, 1000);

console.log('End');

// 출력 결과
Start
End
Timeout
  1. console.log('Start')가 호출 스택에서 실행됩니다.
  2. setTimeout은 비동기 작업으로 백그라운드에 전달됩니다.
  3. console.log('End')가 호출 스택에서 실행됩니다.
  4. 1초 후, 타이머의 콜백이 태스크 큐로 이동합니다.
  5. 호출 스택이 비어 있으면 이벤트 루프가 태스크 큐의 콜백을 호출 스택으로 이동시켜 실행합니다.

4. 마이크로 태스크와 매크로 태스크

태스크(Task)는 실행 우선순위에 따라 두 가지로 나뉩니다.

  • 매크로 태스크(Macro Task): setTimeout, setInterval, setImmediate, I/O 작업 등이 포함됩니다.
  • 마이크로 태스크(Micro Task): Promise의 then, catch, finally, async/await 등이 포함됩니다.

그럼 둘 중에 어떤게 더 우선순위가 높나요❓

  • 마이크로태스크 큐가 매크로태스크 큐보다 우선순위가 높습니다.
  • 이벤트 루프는 콜 스택이 비어있는 시점에 마이크로태스크 큐에 있는 모든 작업들을 먼저 처리하고 매크로태스크의 작업을 실행합니다.

실행 순서 예시

console.log('Start');

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

Promise.resolve().then(() => {
  console.log('Promise');
});

console.log('End');

// 출력 결과
Start
End
Promise // Promise가 SetTimeout보다 먼저 실행
Timeout
  1. 호출 스택에 console.log('Start')가 추가되어 바로 실행됩니다.
    실행이 끝난 후, 호출 스택에서 제거됩니다.
  2. setTimeout 함수가 호출 스택에 추가됩니다.
    이 함수는 브라우저나 Node.js의 백그라운드 환경으로 전달되어 타이머가 설정됩니다.
    setTimeout 자체는 실행을 마친 뒤 호출 스택에서 제거됩니다.
    타이머가 만료되면, 해당 콜백이 매크로 태스크 큐(Macro Task Queue)에 저장됩니다.
  3. Promise.resolve().then이 호출 스택에 추가됩니다.
    then에 전달된 콜백 함수는 마이크로 태스크 큐(Micro Task Queue)에 저장됩니다.
    Promise 처리 자체는 완료된 뒤 호출 스택에서 제거됩니다.
  4. 호출 스택에 console.log('End')가 추가되어 바로 실행됩니다.
    실행이 끝난 후, 호출 스택에서 제거됩니다.
  5. 호출 스택이 비어 있는지 이벤트 루프가 확인합니다
    먼저 마이크로 태스크 큐를 확인하고 대기 중인 Promise의 콜백을 호출 스택으로 이동시켜 실행합니다.
    Promise 콜백이 실행되어 "Promise"가 출력되고 호출 스택에서 제거됩니다.
  6. 이벤트 루프는 마이크로 태스크 큐의 작업을 모두 완료해 매크로 태스크 큐를 확인합니다.
    setTimeout의 콜백이 호출 스택으로 이동 후 실행되어 "Timeout"이 출력되고 호출 스택에서 제거됩니다.

 

 

 

요약

  • 자바스크립트는 단일 스레드 기반으로 동작하며, 비동기 작업 처리를 위해 이벤트 루프를 사용합니다.
  • 이벤트 루프는 호출 스택과 태스크 큐를 관리하여 비동기 작업을 실행합니다.
  • 태스크는 매크로 태스크와 마이크로 태스크로 구분되며, 마이크로 태스크가 우선 실행됩니다.
 

 

추천글

 

[JS] Promise.all 과 Promise.allSettled 차이

JavaScript의 비동기 처리를 할 때, 여러 Promise를 동시에 처리해야 하는 경우가 많습니다. 이때 자주 사용하는 두 가지 메서드가 Promise.all과 Promise.allSettled입니다.목차 1. Promise.all 2. Promise.allSettled 3.

dev-hpk.tistory.com

 

 

[Axios] interceptors 적용 - 토큰 재발급

저번 포스팅에서 retryFetch라는 메서드를 만들어서 요청을 보내고, response의 status를 확인해 토큰을 재발급했다.const retryFetch = async ( url: string, options: RequestInit): Promise => { const response = await fetch(url, o

dev-hpk.tistory.com