Published on

비동기 프로그래밍

비동기 프로그래밍

동기 처리와 비동기 처리

  1. 함수를 호출하면 함수 코드가 평가되어 함수 실행 컨텍스트가 생성
  2. 생성된 함수 실행 컨텍스트는 실행 컨텍스트 스택(= 콜 스택)에 푸시되고 함수 코드가 실행됨
  3. 함수 코드의 실행이 종료되면 함수 실행 컨텍스트는 실행 컨텍스트 스택에 팝되어 제거

싱글 스레드

  • 자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 가짐(⭐⭐)
  • 동시에 2개 이상의 함수를 동시에 실행할 수 없으므로 실행 중인 실행 컨텍스트를 제외한 모든 실행 컨텍스트는 실행 대기 중인 태스크들임
  • 싱글 스레드 방식으로 동작하므로 처리에 시간이 걸리는 태스크를 실행하는 경우에는 블로킹이 발생함
javascript_asynchronous_1
  • 현재 실행 중인 태스크가 종료할 때까지 다음에 실행될 태스크가 대기하는 방식을 동기 처리라고 함
    • 순서대로 하나씩 처리하므로 실행 순서가 보장된다는 장점이 있지만, 앞선 태스크가 종료할 때까지 이후 태스크들이 블로킹

setTimeout을 이용한 비동기 처리

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

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

// 타이머 함수 setTimeout은 일정 시간이 경과한 이후에 콜백 함수 foo를 호출
// 타이머 함수 setTimeout은 bar 함수를 블로킹 하지 않음
setTimeout(foo, 3 * 1000);
bar();
// bar 호출 -> (3초 경과 후) foo 호출
  • setTimeout 함수는 태스크를 블로킹하지 않고 곧바로 실행함
    • 현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식을 비동기 처리라고 함
javascript_asynchronous_2

타이머 함수인 setTimeoutsetInterval, HTTP 요청, 이벤트 핸들러는 비동기 처리 방식으로 동작함

이벤트 루프와 태스크 큐

  • 자바스크립트 엔진이 싱글 스레드로 동작하지만 브라우저 동작하는 것을 보면 많은 태스크가 동시에 처리되는 것처럼 느껴지는데, 이런 동시성을 지원하는 것이 이벤트 루프
javascript_asynchronous_3
  • 이벤트 루프와 브라우저 환경은 위의 그림처럼 표현할 수 있음
  • 구글의 V8 자바스크립트 엔진을 비롯한 대부분의 자바스크립트 엔진은 크게 2개 영역으로 구분할 수 있음

자바스크립트 엔진 영역

태스크가 요청되면 콜 스택을 통해 요청된 작업을 순차적으로 실행함

  • 콜 스택(Call Stack)
    • 소스코드 평가 과정에서 생성된 실행 컨텍스트추가되고 제거되는 스택 자료구조를 콜 스택이라 함
  • (Heap)
    • 객체가 저장되는 메모리 공간으로서 콜 스택의 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조
      • 객체는 원시 값과 달리 크기가 정해져 있지 않음
    • 메모리 공간의 크기를 런타임에 결정(동적 할당)해야 함
      • 객체가 저장되는 메모리 공간인 힙은 구조화되어 있지 않음

브라우저 환경

소스코드의 평가와 실행을 제외한 모든 처리는 자바스크립트 엔진을 구동하는 환경인 브라우저 또는 Node.js가 담당

  • 태스크 큐(task queue/event queue/callback queue)
    • setTimeout이나 setInterval과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역
  • 이벤트 루프(event loop)
    • 콜 스택에 현재 실행 중인 실행 컨텍스트가 있는지, 태스크 큐에 대기 중인 함수(콜백 함수, 이벤트 핸들러 등)가 있는지 반복해서 확인
      • 콜 스택이 비어 있어 있거나 태스크 큐에 대기중인 함수가 있다면 이벤트 루프는 순차적으로 태스크 큐에 대기 중인 함수를 콜 스택으로 이동시킴

실행 순서

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

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

setTimeout(foo, 0); // 0초(실제는 4ms) 후에 foo 함수가 호출됨
bar();
  1. 전역 코드가 평가되어 전역 실행 컨텍스트가 생성되고 콜 스택에 푸시됨
  2. 전역 코드가 실행되어 setTimeout 함수 호출
    • setTimeout 함수의 함수 실행 컨텍스트가 생성되고 콜 스택에 푸시되어 현재 실행 중인 실행 컨텍스트가 됨
  3. setTimeout 함수가 실행되면 콜백 함수를 호출 스케줄링하고 종료되어 콜 스택에서 팝됨
    • 타이머 설정과 타이머가 만료되면 콜백 함수를 태스크 큐에 푸시하는 것은 브라우저의 역할
  4. 브라우저가 수행하는 4-1과 자바스크립트 엔진이 수행하는 4-2는 병행처리
    • 브라우저
      • 타이머를 설정하고 타이머의 만료를 기다리고 만료된 후 콜백 함수 foo가 태스크 큐에 푸시됨
      • 4ms 후에 콜백 함수 foo가 태스크 큐에 푸시되어 대기하게 됨
    • 자바스크립트 엔진
      • bar 함수가 호출되어 bar 함수의 함수 실행 컨텍스트가 생성되고 콜 스택에 푸시되어 현재 실행 중인 실행 컨텍스트가 됨
      • bar 함수가 종료되어 콜 스택에서 팝되는데, 이때 브라우저가 설정한 타이머가 4ms 경과했다면 foo 함수는 태스크 큐에서 대기중인 상태
  5. 전역 코드 실행 종료 후 전역 실행 컨텍스트가 콜 스택에서 팝됨
  6. 이벤트 루프에 의해 콜 스택이 비어 있음이 감지되고 태스크 큐에서 대기 중인 콜백 함수 foo의 함수 실행 컨텍스트가 생성되고 콜 스택에 푸시되어 현재 실행 중인 실행 컨텍스트가 됨
    • 자바스크립트 엔진싱글 스레드로 동작
    • 브라우저멀티 스레드로 동작

Referenced

  • 이응모, 『모던 자바스크립트 Deep Dive』, 위키북스(2022.4.25), 809 ~ 815p