비동기 프로그래밍

1Hoit·2023년 2월 19일
0

자바스크립트

목록 보기
19/25

들어가기전

자바스크립트 엔진은 단 하나의 실행 컨텍스 스택을 갖는다.
그러므로 싱글 스레드 방식으로 동작한다.
한번에 하나의 태스크만 실행할 수 있기 때문에 처리에 시간이 걸리는 태스크를 실행하는 경우 블로킹(작업중단)이 발생한다.

  • 이와 같은 방식을 동기 처리라고 한다.
  • 동기 처리 방식은 테스크를 순서대로 하나씩 처리하므로 실행 순서가 보장된다.
  • 앞선 태스크가 종료할 때까지 이후 태스크들이 블로킹되는 단점이 있다.

자바스크립트 정의를 살펴보면?

자바스크립트는 싱글 쓰레드 기반이며 논 블로킹 방식의 비동기적인 동시성 언어이며 콜 스택, 이벤트 루프와 콜백 큐 그리고 여러가지 다른 API들을 가지고 있다.

  • 즉 자바스크립트 자체는 싱글 스레드 기반이지만
    다양한 API들을 가지고 있어 비동기 처리방식을 사용할 수 있다!

비동기 프로그래밍

비동기 처리 방식은 현재 실행 중인 태스크가 종료되지 않은 상태라도 다음 태스크를 실행하므로 블로킹이 발생하지 않지만
실행 순서가 보장되지 않는 단점이 있다.

  • 비동기 처리 함수는 전통적으로 콜백 패턴을 사용한다.
  • 타이머 함수인 setTimeout , setInterval, HTTP 요청, 이벤트 핸들러 등은 비동기 처리 방식으로 동작한다.

이벤트 루프와 태스크 큐

비동기 처리는 이벤트 루프와 태스크 큐와 깊은 관계가 있다.

각각의 역할

콜 스택 : 함수가 호출될 때 실행 컨텍스트(execution context)가 쌓이는 영역
: 메모리가 할당되는 부분

그림을 보고 구성을 보자.

  • 구글의 자바스크립트 엔진 V8 : 힙과 콜스택을 가짐
  • 웹 브라우저에서 제공하는 Web API : DOM, Ajax, setTimeout 등을 제공.
  • 이벤트 루프와 콜백 큐도 있다.

1. 콜스택 (Call Stack)

예를 들어보며 보는 것이 이해가 빠르다.

function multiply(a,b) {
  return a*b;
}

function square(n) {
  return multiply(n,n);
}

function printSquare(n) {
  var squared = square(n);
  console.log(squared);
}

printSquare(4);

코드를 실행하면 아래와 같이 콜스택에 쌓인다.
즉, 호출한 순서대로 쌓이는 것을 알 수 있다.
(제일 처음에 실행할 때는 메인함수의 execution context가 추가되는 것을 기억하자.) 이런 방식이 기본적인 콜 스택이며 웹 브라우저에서 에러가 발생했을 때 그때까지의 호출순서를 보여주는 것도 콜 스택을 통해서 하는 것이다.
만약 콜 스택을 재귀함수로 호출하게 되면 아래와 같은 경고문구가 나오면서 종료된다.
RangeError: Maximum call stack size exceeded!!

블로킹(Blocking)

콜 스택에 현재 느리게 동작하는 작업이 남아있는 것을 말한다. 대표적인 예시로 네트워크 요청 혹은 이미지 프로세싱 등이 있다

var foo = $.getSync('//foo.com');
var bar= $.getSync('//bar.com');
var qux = $.getSync('//qux.com');

console.log(foo);
console.log(bar);
console.log(qux);

jQuery를 통해서 동기적으로 네트워크 요청을 3번 하는 코드이며 위와 같이 콜 스택이 진행된다.

네트워크 요청은 느린 작업이기 때문에 다음 작업이 곧장 실행되지 않고 현재 진행되는 작업이 끝날 때까지 기다린 후에 다음 작업이 실행되고 있다.
이 방식이 문제가 되는 이유는 바로 코드가 웹 브라우저에서 실행되고 있기 때문이다.
느린 작업으로 인해 blocking이 발생하게 되면 웹 브라우저는 렌더링을 하지 못하고 다른 코드 또한 실행할 수 없게 된다.
즉, 사용자의 경험을 막게 된다.
따라서 위를 해결하기 위한 방식인 “비동기 콜백” 이 등장한다.

비동기 콜백(Asynchronous Callback)

일반적으로 비동기 콜백을 설명할 때 가장 많이 사용하는 함수가 바로 setTimeout 이다.
주어진 시간만큼 기다렸다가 콜백함수를 실행하는 이 함수는 JS엔진인 V8에 내장되어 있지 않고, 웹 브라우저에서 제공하는 Web API에 존재한다. 아래 코드를 보면서 비동기 콜백이 어떻게 이루어지는지 확인하자.

console.log('First Stack');
setTimeout(function callback(){
  console.log('Asynchronous Callback');
}, 3000);
console.log('Second Stack');

지금까지 배운 콜 스택의 개념을 활용하면 콜 스택에 차례대로 쌓일 것 같지만, V8의 소스코드에는 setTimeout 함수가 없기 때문에 웹 브라우저가 대신 실행해주어야 한다.
여기서 바로 “동시성” 개념이 나온다.
즉, JS가 싱글 쓰레드 기반임에도 불구하고 동시성 언어라고 부르는 이유는 웹 브라우저가 제공하는 API를 통해 동시에 작업을 할 수 있기 때문이다.

그림에서 볼 수 있듯 콜스택 영역에는 setTimeout 함수가 없기 때문에 웹 브라우저인 WebAPI 쪽에서 setTimeout 함수를 가지고 있다.

그러므로 처음엔 순서대로 쌓이다가 setTimeout 함수를 웹 브라우저에게 맡기고 두 번째 log 를 쌓는다. 따라서 아래와 같이 먼저 출력되는 것이다.
First Stack
Second Stack

그리고 이제 콜 스택에서 main() 을 제외한 모든 함수가 리턴되고 Web API의 setTimeout 타이머가 종료되면 해당 콜백이 콜백 큐(태스크 큐)로 전달된다.
이제 여기서 이벤트 루프의 역할이 나오는데
이벤트 루프는 콜 스택과 콜백 큐를 감시하는 역할로 콜백 큐에 함수가 존재하고 콜 스택이 비었다면 콜백 큐에서 콜백을 꺼내 콜 스택에 넣어주는 역할을 한다.
아래와 같이 동작하는 것이다.

즉, 순서를 보자면
콜스택 처리 -> 이벤트 루프의 감시 -> 콜스택이 비었다면 Web API에서 setTimeout을 태스크큐 (콜백 큐)로 이동 -> 콜백 큐에 함수가 존재하고 콜 스택이 비었다면 콜백 큐에서 콜백을 꺼내 콜 스택에 넣기

이 과정을 통해 어떤 코드를 바로 실행시키지 않고 특정하게 순서를 조정하고 싶을 때 다음과 같이 할 수 있다.

console.log('First Stack');
setTimeout(() => console.log('Third Stack'), 0);
console.log('Second Stack');

//출력
First Stack
Second Stack
Third Stack

이것이 가능한 이유는 웹 브라우저가 담당했다가 콜백 큐로 가고 그 다음에 이벤트 루프가 콜백 큐에서 콜 스택으로 옮기는 과정을 거치기 때문이다. 따라서 setTimeout 을 연속으로 호출하는 경우 또한 기대하는 지연 시간과 다른 값이 나올 수 있다.


자 이제 다시 돌아가서 좀더 자세하게 살펴보고 정리해 보자.

이벤트 루프

브라우저에 내장되어 있는 기능으로 자바스크립트의 동시성을 지원한다.
위에서 언급했듯이
콜 스택과 콜백 큐를 감시하는 역할로 콜백 큐에 함수가 존재하고 콜 스택이 비었다면 콜백 큐에서 콜백을 꺼내 콜 스택에 넣어주는 역할을 한다

1. 콜스택
소스코드 평가 과정에서 생성된 실행 컨텍스트가 추가, 제거되는 실행컨텍스트 스택이다.
2. 힙
객체가 저장되는 메모리 공간으로 콜 스택의 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조한다.

  • 자바스크립트 엔진
    단순히 태스크가 요청되면 콜 스택을 통해 요청된 작업을 순차적으로 처리하기만 한다.

  • 브라우저 , Node.js 와 같은 환경
    비동기 처리에서 소스코드의 평가와 실행을 제외한 모든 처리는 자바스크립트 엔진을 구동하는 환경인 브라우저 , Node.js가 담당한다.
    • 위에서 예를 들면 setTimeout의 콜백함수의 평가와 실행은 자바스크립트 엔진이 담당하지만
      호출 스케줄링을 위한 타이머 설정과 콜백 함수의 등록은 브라우저, Node.js가 담당한다.
    • 이렇게 브라우저가 담당하기 위해 브라우저 환경에서는 태스크 큐, 이벤트 루프를 제공한다.

비동기 처리를 위해 브라우저 환경에서 제공하는 요소

  • 태스크 큐 (콜백 큐)
    비동기 함수의 콜백함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역
  • 이벤트 루프
    콜 스택에 현재 실행 중인 실행 컨텍스트가 있는지, 태스크 큐에 대기 중인 함수(이벤트 핸들러, 콜백함수 등)가 있는지 확인하고
    콜 스택이 비어있고 태스크 큐에 대기중인 함수가 있다면 순차적으로 태스크 큐에 대기중인 함수를 콜스택으로 이동 시킨다.

마지막으로 정리

자바스크립트는 싱글 스레드 방식으로 동작한다.

  • 싱글 스레드 방식으로 동작하는 것은 브라우저가 아니라
    브라우저에 내장된 자바스크립트 엔진이다.
    • 자바스크립트 엔진 = 싱글스레드 방식으로 동작
    • 브라우저 = 멀티 스레드로 동작

싱글스레드 방식으로 동작하는 자바스크립트 엔진의 비동기 처리를 지원하기 위해
브라우저와 Nodejs 같은 환경이 비동기 처리를 위한 다양한 것들(이벤트 루프, 태스크 큐 등..)을 지원해준다.

profile
프론트엔드 개발자를 꿈꾸는 원호잇!

0개의 댓글