이벤트 루프

Eye0n·2021년 8월 13일
4

javascript

목록 보기
7/7

자바스크립트는 싱글 스레드 기반 프로그래밍 언어입니다.

싱글 스레드란 한 번에 하나의 작업만 할 수 있음을 의미합니다.

따라서 하나의 작업이 오래 걸리면 다음 작업이 Blocking되어 suspend time이 늘어나게 됩니다.

이를 해결하기 위해 자바스크립트는 이벤트 루프를 사용하여 비동기 방식으로 Non-Blocking IO을 지원합니다.

Non-blocking I/O(Asynchronous I/O 혹은 Non-sequential I/O) : 입출력 처리는 시작만 해둔 채 완료되지 않은 상태에서 다른 처리 작업을 계속 진행할 수 있도록 멈추지 않고 입출력 처리를 기다리는 방법을 말한다.

JS엔진은 비동기 관련 작업을 할 수 가 없습니다.
이를 해결하기 위해 브라우저에서 지원하는 WebAPI 기능들을 사용하는데요.

바로 DOM API와 setTimeout, HTTP requests(AJAX)등이 있습니다.

Event Loop가 브라우저와 Call Stack 간의 중재 역할을 하면서 Async(비동기), Non-Blocking 동작을 수행하게 만들 수 있습니다.

먼저 자바스크립트 엔진부터 보겠습니다.

자바스크립트 엔진

대표적으로 JS 엔진은 구글의 V8 엔진이 있습니다.
크롬과 노드 안에서 작동합니다.

자바스크립트 엔진은 다음과 같은 두 가지 구성 요소를 가집니다.

자바스크립트 엔진 구성 요소

  • Memory Heap : 메모리 할당이 일어나는 곳

    Heap : 구조화되지 않은 넓은 메모리 영역 -> 객체(변수, 함수등)들이 담긴다.

  • Call Stack : 실행될 코드의 한 줄 단위로 할당되어 실행


런타임

위 그림에서도 알 수 있지만 자바스크립트에는 이벤트 루프가 없습니다.
V8과 같은 자바스크립트 엔진은 단일 호출 스택을 사용하고, 요청이 들어오면 그 순서에 따라 순차적으로 처리할 뿐입니다.

그러면 비동기 요청은 어떻게 처리할까요?

자바스크립트 엔진을 구동하는 런타임 환경(브라우저나 Node.js)이 담당합니다.

위에서 말했듯이 JS가 비동기 처리를 위해 JS엔진과 브라우저 WebAPI를 함께 사용합니다.

아래 그림은 이를 이해하기 쉽게 표현한 것입니다.

Call Stack

Call Stack 은 프로그램 상에서 어떤 작업을 먼저 수행할지에 대한 스케줄링된 스택 자료구조입니다.

스택은 LIFO(Last In First Out)를 가진 자료구조입니다.

예를 들어 first함수가 호출되면 Call Stack에 쌓이고(push) 함수 실행이 완료되면 제거됩니다.(pop)

Web APIs

: 각 요청에 따른 비동기 처리를 기능을 지원합니다.

  • DOM(document)
  • AJAX(XMLHttpRequest)
  • Timeout(setTimeout)

Callback Queue

Queue(First In First Out) 자료구조이며, 비동기 처리가 끝난 후 실행되어야 할 콜백 함수가 차례로 할당됩니다.
callback queue는 순서에 맞게 할당되는 콜백함수들 중에서 우선순위에 따른 처리를 합니다.
ex) Promise.then()이 setTimeout()보다 우선 처리됨

callback queue 구성요소

  • task queue(=event queue) : ex) event, setTimeout
  • microtask queue(job queue) : ex) promise
  • animation frames

callback queue 우선순위

microtask queue > animation frames > task(event) queue

Event Loop

Call Stack이 비어있는 지 확인 후
Callback Queue에 할당된 콜백 함수를 순서에 맞춰 Call Stack에 할당해줍니다.
순서에 맞춰 할당해주지만 Callback Queue의 우선순위에 따라 순서가 달라질 수 있습니다.

동기

동기 작업에 대한 예시입니다.

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);
  • step1. printSquare(5)가 호출되어 콜 스택에 push
  • step2. printSquare함수 내부 multiply함수가 호출되어 콜 스택에 push 후 실행되어 pop
  • stpe3. printSquare함수 내부 console.log가 호출되어 콜 스택에 push
  • step4. console.log가 실행되어 pop
  • step5. printSquare함수가 끝나서 pop

비동기

아래 그림은 비동기 작업에 대한 순서를 나타냅니다.

  1. 함수 호출로 인해 콜 스택에 push
  2. 콜 스택에 push된 함수가 비동기 처리인 경우 WebAPI로 전달
    (여기서 setTimeout의 경우 해당 초만큼 대기만 합니다.)
  3. 콜백 함수가 Callback Queue로 이동
  4. Event Loop가 Call Stack이 비어있는 것을 확인 후 Queue에서 콜백함수를 pop하여 Call Stack에 push하여 실행
const foo = () => console.log('First');
const bar = () => setTimeout(() => console.log('Second'), 500);
const baz = () => console.log('Third');

bar();
foo();
baz();
  • step1. bar함수 호출로 Call Stack에 push
    bar함수 반환 값이 setTimeout이므로 Call stack에 push
    setTimeout의 콜백함수가 WebAPI에게 전달되고 bar함수와 setTimeout함수 pop
    (WebAPI에서 0.5초만큼 대기 후 Callback Queue로 전달)
  • step2. 타이머가 동작하는 동안 foo함수 Call Stack에 push
  • step3. foo 내부 함수 console.log실행으로 'First'출력 후 foo함수 pop
  • step4. baz함수 Call Stack에 push
  • step5. baz 내부 함수 console.log실행으로 'Third'를 출력 foo함수 pop
  • step6. Event Loop가 Call Stack이 비어있는 걸 확인하여 Callback Queue에서 대기 중인 setTimeout의 콜백함수를 Call stack에 push
  • step7. setTimeout의 콜백함수가 실행되어 'Second'를 출력 후 pop


reference

✨♻️ JavaScript Visualized: Event Loop
https://new93helloworld.tistory.com/358

1개의 댓글

comment-user-thumbnail
2021년 9월 26일

움짤 잘만드셨네요 ㅎㅎ 단박에 이해하고 갑니다~

답글 달기