[Javascript] 비동기를 이해하기 위한 개념 (2) - 이벤트 루프

Yeojin Choi·2022년 1월 4일
0

Javascript

목록 보기
8/11

이벤트 루프

  • Run-to-Completion : 자바스크립트의 함수가 실행되는 방식. 하나의 함수가 실행되면 이 함수의 실행이 끝날 때까지는 다른 어떤 작업도 중간에 끼어들지 못함
  • 자바스크립트는 ‘단일 스레드’ 기반의 언어인데 웹 브라우저가 애니메이션 효과를 보여주면서 사용자 이벤트를 받을 수 있는 이유 ? 이벤트 루프를 이용해 비동기 방식으로 자바스크립트의 동시성(Concurrency) 을 지원하기 때문
  • 동시성에 대한 처리 : 자바스크립트 엔진을 구동하는 환경 (브라우저, Node.js)

브라우저 환경

Call stack

소스코드(전역 코드나 함수 코드) 평가 과정에서 생성된 실행 컨텍스트가 추가되고 제거되는 스택 자료구조

  • 함수의 실행 순서는 콜 스택으로 관리된다
    - 함수 호출 → 함수 코드 평가 → 함수 실행 컨텍스트 생성 → 실행 컨텍스트 스택 (=콜스택) 에 Push 됨 (함수 실행의 시작) → 함수 코드 실행 → 함수 코드 실행 종료 시 콜스택에서 pop 되어 제거됨

  • 자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 가짐 ⇒ 싱글 스레드 방식의 동작
    - 동시에 2개 이상의 함수를 실행할 수 없다
    - 처리에 시간이 걸리는 태스크를 실행하는 경우 blocking 발생
    - 최상위 실행 컨텍스트가 종료되어 콜스택에서 제거되기 전까지는 다른 태스크가 실행되지 않음

Heap

  • 콜 스택의 요소인 실행 컨텍스트가 참조하는 객체가 저장되는 메모리 공간
  • 객체는 원시값과 달리 할당해야할 메모리 공간의 크기가 런타임에 결정(동적 할당)됨 ⇒ 힙은 구조화 되어있지 않음!

Web API

  • 브라우저에서 제공하는 API
    • DOM API, 타이머 함수, HTTP 요청

Task Queue

  • setTimeout , setInterval 과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역 cf) 프로미스의 후속 처리 메서드가 일시적으로 보관되는 영역은 마이크로태스크 큐

Event Loop

  • 콜 스택에 현재 실행중인 실행 컨텍스트가 있는지 , 태스크 큐에 대기중인 함수가 있는지 반복해서 확인한다.
  • 콜스택이 비어있고 태스크 큐에 대기중인 함수가 있다면 이벤트 루프는 순차적으로 태스크 큐에 대기중인 함수를 콜스택으로 이동시킨다.
  • “자바스크립트는 싱글 스레드로 동작한다” 라는 말의 찐 의미 : 브라우저에 내장된 자바스크립트 엔진은 싱글 스레드, 브라우저는 멀티 스레드

Node.js 환경

  • Node.js는 비동기 IO를 지원하기 위해 libuv 라이브러리를 사용하며, 이 libuv가 이벤트 루프를 제공
  • 자바스크립트 엔진은 비동기 작업을 위해 Node.js의 API를 호출하며, 이때 넘겨진 콜백은 libuv의 이벤트 루프를 통해 스케쥴되고 실행됨

setTimeout(fn,0)

  • setTimeout 함수는 콜백 함수를 바로 실행하지 않고 태스크 큐에 추가한다.
setTimeout(function() {
    console.log('A');
}, 0);
console.log('B');

// B -> A

Promise 와 이벤트 루프

Micro Task

  • 현재 실행되고 있는 작업 바로 다음으로 실행되어야 할 비동기 작업. 일반 태스크보다 더 높은 우선순위를 갖는 태스크
  • 태스크 큐에 대기중인 태스크가 있더라도 마이크로 태스크가 먼저 실행됨
  • 태스크를 기다리기 전에 마이크로 태스크가 있는지를 먼저 확인하고, 마이크로 태스크가 있다면 먼저 모두 수행하고 나서 태스크를 수행함

ex)

setTimeout(function() { // (A)
    console.log('A');
}, 0);
Promise.resolve().then(function() { // (B)
    console.log('B');
}).then(function() { // (C)
    console.log('C');
});

// B -> C -> A
  • setTimeout() : 콜백 A를 태스크 큐에 추가
  • Promise의 then() 메소드 : 는 콜백 B를 태스크 큐가 아닌 별도의 마이크로 태스크 큐에 추가
  • 위의 코드의 실행이 끝나면 태스크 이벤트 루프는 태스크 큐 대신 마이크로 태스크 큐가 비었는지 먼저 확인하고, 큐에 있는 콜백 B를 실행
  • 콜백 B가 실행되고 나면 두번째 then() 메소드가 콜백 C를 마이크로 태스크 큐에 추가
  • 이벤트 루프는 다시 마이크로 태스크를 확인하고, 큐에 있는 콜백 C를 실행한다.
  • 마이크로 태스크 큐가 비었음을 확인한 다음 태스크 큐에서 콜백 A를 꺼내와 실행

위 상황처럼 마이크로 태스크이냐 일반 태스크이냐에 따라 실행되는 타이밍이 달라지기 때문에 마이크로 태스크가 계속돼서 실행될 경우 일반 태스크인 UI 렌더링이 지연되는 현상이 발생할 수 있다.

References
https://meetup.toast.com/posts/89
https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif
모던 자바스크립트 Deep Dive

profile
프론트가 좋아요

0개의 댓글