42장 비동기 프로그래밍

Eriniss·2023년 1월 3일
0
post-thumbnail

42.1 동기 처리와 비동기 처리

비동기 처리 형식이 나온 배경을 쉽게 설명하기 위해 그림에 빗대어 보겠다.
예를 들어 한 도화지 위에 토끼와 여우를 그린다고 가정해 보자. 일반적인 사람들은 토끼를 다그리고 여우를 그리는 방식을 사용한다. 이를 동기 처리라고 한다. 그런데 당신은 천재 미술가이기 때문에 이런 방식을 지루하게 느낀다. 무려 양손으로 토끼와 여우를 동시에 그린다! 이런 방식을 비동기 처리라고 한다.

비동기 처리를 이해하기 위해서는 실행 컨텍스트에 대한 이해가 필요하다.먼저 자바스크립트 엔진은 싱글 스레드로 동작한다. 이는 한 번에 하나의 태스크만을 실행할 수 있다.

동기 처리 방식은 실행 순서가 보장된다는 장점이 있지만 태스크가 종료할 때까지 이후 태스크들이 블로킹 되는 단점, 즉 속도가 느려질 수 있다.

반면 비동기 처리 방식은 실행 순서가 보장지 않는 단점이 있지만 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 생행할 수 있다. 따라서 속도가 빠르다. 전통적인 방식으로 콜백 함수가 사용되며 setTimeout, setInterval 등과 같은 타이머 함수 역시 비동기 처리 방식으로 처리 된다.

42.2 이벤트 루프와 태스크 큐

자바스크립트의 동시성을 지원하는 것을 이벤트 루프라고 부르며 이는 다음과 같이 이루어져 있다.

  • 콜 스택
    우리가 실행 컨텍스트에서 배웠던 실행 컨텍스트 스택이 바로 콜 스택이다.


  • 힙은 객체가 저장되는 메모리 공간이다. 콜 스택의 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조한다.

비동기 처리에서 소스코드의 평가와 실행을 제외한 모든 처리는 자바스크립트 엔진을 구동하는 환경인 브라우저 또는 Node.js가 당담한다. 이를 위해 브라우저 환경은 태스크 큐와 이벤트 루프를 제공한다.

  • 태스크 큐
    비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역이다.

  • 이벤트 루프
    실행 컨텍스트와 태스크 큐를 반복해서 확인하며 스택 형식으로 실행 순서를 결정한다.

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

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

setTimeout(foo, 0); // 0초(실제로 4ms)후 실행
bar(); // 즉시 실행
  1. 전역 코드 평가 및 콜 스택에 푸시

  2. 전역 코드 실행 전 setTimeout 함수 호출. 타이머 함수도 함수이므로 함수 실행 컨텍스트 생성

  3. setTimeout 함수를 브라우저에 의해 태스크 큐에 푸시

  4. 4-1과 4-2는 병행처리 됨
    4-1 브라우저는 타이머 설정 후 타이머의 만료를 기다림. 이후 타이머 만료 시 콜백 함수 foo 가 태스크 큐에 푸시. 4ms 경과 후 콜백 함수 foo가 태스크 큐에 푸시되어 대기
    4-2 bar 함수가 호출되어 실행 컨택스트가 생성되고 콜 스택에 푸시. 이때 브라우저가 타이머를 설정한 후 4ms가 경과했다면 foo 함수는 아직 태스크 큐에서 대기중

  5. 전역 코드 실행 종료 및 전역 실행 컨텍스트가 콜 스택에서 팝 됨

  6. 콜 스택이 비어있음이 감지되고 태스크 큐에서 대기 중인 콜백 함수 foo가 이벤트 루프에 의해 콜 스택에 푸시. 다시 말해, 콜백 함수 foo의 함수 실행 컨텍스트가 생성되고 콜 스택에 푸시되어 현재 실행 중인 실행 컨텍스트가 됨.

앞서 배운 실행 컨텍스트를 이해 했다면 비동기 처리 방식의 원리도 쉽게 할 수 있다. 쉽게 말해 태스크 큐는 나중에 처리할 함수를 저장하는 저장함으로 생각하면 쉽고 저장함에서 함수를 꺼내 실행할 순서를 정하는 것은 이벤트 루프의 역할이다.

내 방식대로 풀어 써보자. 앞서 실행 컨텍스트에서 자바스크립트엔진은 런타임 이전에 코드 평가를 한다고 했다. 평가 기간에 setTimeout과 같은 함수를 평가하고 콜백 함수를 태스크 큐(저장함)에 푸시한다. setTimeout 함수를 푸시 해버렸으므로 비동기 처리가 아닌 동기 처리 함수들부터 일단 실행한다. 위 코드에선 bar()가 해당된다. bar가 실행되었다. 4ms가 지나면 브라우저에 의해 태스크 큐에 있는 콜백함수를 가져온다. 만약 bar의 런타임이 4ms를 넘긴다면 foo는 여전히 태스크 큐에서 대기하다가 bar의 런타임이 끝나면 실행된다.

한번에 처리하는 것이 아닌 저장고에 넣었다가 꺼내쓰는 방식이라 이해하면 쉽다. 실행 컨텍스트는 싱글 스레드 방식으로 동작하지만 브라우저는 멀티 스레드로 동작하기 때문에 이와 같은 비동기 처리방식이 가능하다.

0개의 댓글