업데이트:

[01] 동기 처리와 비동기 처리

앞선 23장 실행 컨텍스트 파트에서 함수가 호출된 순서대로 실행 컨텍스트 스택에 푸시되고, 함수가 종료되면 실행 컨텍스트 스택에서 제거된다고 배운 바 있다.

자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택을 갖기 때문에 여러 함수를 실행한다고 해도 동시에 여러 함수를 실행할 수 없다.

즉, 자바스크립트는 한 번에 하나의 태스크만 실행할 수 있는 싱글 스레드 방식으로 동작한다.

🎯 동기 처리

function sleep(func, delay) {
	const delayUntil = Date.now() + delay;

	while (Date.now() < delayUntil);
	
	func();
}

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

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

sleep(foo, 3 * 1000);

bar();

현재 시각으로부터 3초가 지났을 때 함수를 호출하는 코드이다.

foo함수는 sleep함수 안에서 3초 후에 호출되고 bar 함수는 sleep 함수의 실행이 종료된 이후에 호출되므로 3초 이상 호출되지 못하고 블로킹(작업 중단)된다.

위와 같은 방식은 순서대로 하나씩 처리하기 때문에 동기 처리라고 한다.

그런데 이러한 방식은 앞선 함수의 실행이 종료될 때까지 이후 함수들이 기다려야한다는 단점이 있다.

🎯 비동기 처리

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

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

setTimeout(foo, 3 * 1000);
bar();

setTimeout 함수는 sleep 함수와 유사하게 3초 후에 foo 함수를 호출하지만 bar함수의 동작이 블로킹 되지 않고 곧바로 실행된다.

이러한 방식을 비동기 처리라고 한다.

하지만 이러한 비동기 방식은 실행 순서가 보장되지 않는다는 단점이 있다.

앞선 장에서 배운 타이머 함수인 setTimeout과 setInterval, HTTP 요청, 이벤트 핸들러가 대표적인 비동기 처리 방식이다.

[02] 이벤트 루프와 태스크 큐

자바스크립트가 싱글 스레드 방식이라고 했는데 실제로 브라우저가 한가지 일을 완료하고 다음 일을 할 수 있다면 성능도 떨어지고 엄청난 비효율일 것이다.

지금부터 자바스크립트의 동시성을 지원하는 이벤트 루프에 대해 알아보자 !

Untitled

자바스크립트 엔진은 콜 스택, 힙으로 구분된다.


  • 콜 스택(call stack) : 실행 컨텍스트 스택
  • 힙(heap) : 객체가 저장되는 메모리 공간, 실행 컨텍스트는 힙에 저장된 객체를 참조한다.

자바스크립트 엔진은 단순히 태스크가 요청되면 콜 스택을 통해 요청된 작업을 동기적으로 실행한다.

그런데 비동기 방식으로 동작하는 setTimeout 함수를 만나면 평가와 실행까지만 자바스크립트 엔진이 담당하고 호출 스케줄링은 브라우저나 Node.js가 담당한다.

이러한 비동기 방식을 처리하기 위해 브라우저는 태스크 큐와 이벤트 루프를 제공한다.


  • 태스크 큐 (task/event/callback queue) : setTimeout 같은 비동기 함수의 콜백 함수나 이벤트 핸들러가 일시적으로 보관되는 영역
  • 이벤트 루프 (event loop)

    콜 스택에 실행중인 실행 컨텍스트가 있는지 태스크 큐에 대기 중인 함수가 있는지 반복해서 확인한다.

    콜 스택이 비어 있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적으로 태스크 큐에 대기 중인 함수를 콜 스택으로 이동시켜 실행한다.


실제 어떻게 이벤트 루프가 동작하는지 코드를 통해 보자

// 1. 전역 코드가 평가되어 전역 실행 컨텍스트가 생성되고 콜 스택에 푸시된다.
function foo() {
	console.log('foo');
}
// 2. 
function bar() {
	console.log('bar');
}

// 2. setTimeout 함수가 호출, 콜 스택에 푸시
setTimeout(foo, 0); // 3. 콜백 함수를 호출 스케줄링하고 
bar(); 

콜 스택에서는 전역 실행 컨텍스트 ⇒ setTimeout 함수 실행 컨텍스트 ⇒ bar 함수 실행 컨텍스트 순으로 푸시되고 반대 순서대로 제거(pop)될 것이다.

그런데 setTimeout 함수는 일반 함수처럼 실행되고 팝되는 것이 아니라 setTimeout 함수 안에 인자로 넘겨준 foo 함수를 호출 스케줄링하는 과정을 거친다.

이때 호출 스케줄링을 하기 위해 필요한 것이 브라우저의 태스크 큐, 이벤트 루프인 것이다.

그렇다면 브라우저에서 호출 스케줄링은 어떻게 진행될까?

콜 스택에서 setTimeout이 제거 되었지만 사실은 브라우저의 태스크 큐에 푸시되어 대기하고 있다.

전역 코드에서 호출된 함수가 모두 종료되어 콜스택이 비면 그 때 이벤트 루프가 태스크 큐에 있는 setTimeout 함수를 콜 스택으로 이동시켜 실행하는 것이다.

📌 결국, 자바스크립트 엔진은 싱글 스레드로 동작하지만 브라우저는 멀티 스레드로 동작한다.

댓글남기기