
인트로
이 부분을 이해하려면 실행 컨텍스트를 알아야 합니다.
클로저의 문지기가 실행 컨텍스트이기 때문에 저는 그때 실행 컨텍스트 공부를 했습니다.
싱글스레드란 한 번에 하나의 태스크만 실행할 수 있는 방식을 말합니다.
자바스크립트 엔진은 단 하나의 실행 컨텍스트 스펙을 가집니다.
이는 두 개 이상의 함수를 동시에 실행할 수 없다는 말과 같습니다.
현재 실행 중인 실행 컨텍스트를 제외한 모든 실행 컨텍스트는 대기 중인 테스크들입니다.
즉 , 자바스크립트 엔진은 싱글 스레드 방식으로 동작하는 것이죠.
따라서 오래 걸리는 태스크를 실행할 경우 블로킹(작업 중단)이 발생하게 됩니다. (바로 무한 존버 타이밍)
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();
sleep 함수를 호출하면 일정 시간 이후 foo(콜백함수)를 호출합니다.
sleep 함수 실행이 완전히 종료된 후에 bar가 호출됩니다. bar는 위 예제에서 3초 이상 블로킹됩니다.
동기 (무한 존버)
이처럼 현재 실행 중인 태스크가 종료할 때까지 다음에 실행될 태스크가 대기하는 방식을 동기 처리라고 합니다.
실행 순서가 보장되지만 앞선 태스크가 종료할 때까지 아무것도 못합니다.
setTimeout 함수를 사용한 경우는 어떨까요?
function foo() {
console.log('foo 입니다만?');
}
function bar() {
console.log('bar 빨리 실행시켜주세요');
}
setTimeout(foo, 3 * 1000);
bar();
위 예제의 sleep 함수와 유사하게 일정 시간 이후 콜백함수를 호출합니다.
그런데 콘솔을 보니 setTimeout 함수 이후 태스크를 블로킹하지 않고 바로 실행을 해주네요.
비동기 (효율충)
현재 실행 중인 태스크가 종료되지 않은 상태에도 다음 태스크를 바로 실행하는 방식을 비동기 처리라고 합니다.
비동기 처리 방식은 다음 태스크에 대해 블로킹이 발생하지 않지만 태스크의 실행 순서가 보장되지 않습니다.
setTimeout과 setInterval, HTTP 요청, 이벤트 핸들러가 비동기 처리 방식으로 동작합니다.
비동기는 가독성, 예외처리 등 다양한 문제가 있습니다. 이런 문제는 "프로미스"에서 확인해봅시다..^^
이벤트 루프(효율충 도우미)
자바스크립트가 싱글 스레드로 동작합니다. 그렇다면 다양한 태스크를 동시에 처리하려면 어떻게 해야할까요?
이걸 가능하게 해 주는 게 이벤트루프입니다. 자바스크립트의 동시성을 지원하는데요.
이벤트 루프는 브라우저에 내장된 기능입니다.
먼저 브라우저 환경을 살펴봅시다.
자바스크립트 엔진은 크게 '콜 스택'과 '힙'의 영역으로 구분할 수 있습니다.
- 콜 스택 : 소스코드 평가 과정에서 생성된 실행 컨텍스트가 추가되고 제거되는 스택 자료 구조입니다.
실행 컨텍스트 스택이라고도 부릅니다. - 힙 : 객체가 저장되는 메모리 공간입니다. 실행 컨텍스트는 힙에 저장된 객체를 참조합니다.
자바스크립트 엔진은 단순하게 그냥 주는 데로 합니다.
태스크가 요청되면 콜 스택을 통해 요청된 작업을 순차적으로 실행합니다.
비동기 처리에서 소스코드의 평가, 실행을 제외한 모든 처리는 브라우저나 Node.js가 담당합니다.
비동기 방식으로 동작하는 setTimeout의 콜백 함수 평가와 실행은 자바스크립트 엔진이 하지만
호출 스케줄링을 위한 타이머 설정과 콜백 함수의 등록은 브라우저나 Node.js가 하는 것이죠.
(호출 스케줄링이란?)
그래서 브라우저 환경은 태스크 큐와 이벤트 루프를 제공합니다.
태스크 큐(= 콜백 큐)
비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역입니다.
이벤트 루프
콜 스택에 현재 실행 중인 실행 콘텍스트가 있는지, 태스크 큐에 대기 중이 함수가 있는지 반복해서 확인합니다.
만약 콜 스택이 비어 있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적(FIFO)으로 태스크 큐에 대기 중인 함수를 콜 스택으로 이동시킵니다.
즉, 태스크 큐에 일시 보관된 함수들은 비동기 처리 방식으로 동작합니다.
아래 예제를 통해 브라우저 환경에서 어떻게 동작하는지 살펴봅시다.
function foo() {
console.log('foo');
}
function bar() {
console.log('bar');
}
setTimeout(foo, 0);
bar();
- 전역 코드가 평가되어 전역 실행 컨텍스트가 생성됩니다. 생성한 실행 컨텍스트는 콜 스택에 푸시됩니다.
- 전역 코드가 실행되면서 setTimeout 함수를 호출됩니다.
setTimeout 함수의 실행 컨텍스트가 생성된 후 콜 스택에 푸시됩니다.
이때 setTimeout 함수의 함수 실행 컨텍스트는 현재 실행 중인 실행 컨텍스트가 됩니다.
브라우저의 Web API(호스트 객체)인 타이머 함수도 함수이기 때문에 함수 실행 컨텍스트를 생성합니다. - setTimeout 함수가 실행되면 콜백 함수를 호출 스케줄링하고 종료되어 콜 스택에서 pop됩니다.
호출 스케줄링, 즉 타이머 설정과 타이머가 만료되면 브라우저가 콜백 함수를 태스크 큐에 푸시하는 것입니다. - 여기서 브라우저의 수행과 자바스크립트 엔진의 수행이 동시에 처리됩니다
- 브라우저 : 타이머 설정 후 타이머 만료를 기다립니다.
타이머가 만료되면 콜백 함수 foo가 태스크 큐에 푸시됩니다.
지연 시간이 4ms이하인 경우 최소 지연 시간 4ms가 지정됩니다. (0초가 아님)
따라서 4ms 후에 콜백 함수 foo가 태스크 큐에 푸시되어 대기합니다.
(setTimeout 함수로 호출 스케줄링한 콜백 함수는 정확히 지연 시간 후에 호출된다는 보장이 없습니다.
지연 시간 이후에 콜백 함수가 태스크 큐에 푸시되어 대기하게 되지만 스택이 비어야 호출됩니다.
따라서 약간의 시간차가 있음) - 자바스크립트 엔진 : bar 함수가 호출되어 bar 함수의 실행 컨텍스트가 생성된 후 콜 스택에 푸시됩니다.
bar 함수의 실행 컨텍스트는 현재 실행 중인 실행 컨텍스트가 됩니다.
bar 함수가 종료되어 콜 스택에 pop됩니다.
이때 브라우저가 타이머를 설정한 후 4ms가 경과했다면 foo함수는 아직 태스크 큐에서 대기하게 됩니다.
- 브라우저 : 타이머 설정 후 타이머 만료를 기다립니다.
- 전역 코드 실행 종료 후 전역 실행 컨텍스트가 콜 스택에서 pop됩니다. 이제 콜 스택은 빈 상태입니다.
- 이벤트 루프가 콜 스택이 빈 것을 확인하고 태스크 큐에서 대기 중인 콜백 함수 foo를 콜 스택에 푸시합니다.
즉, 콜백 함수 foo의 함수 실행 컨텍스트가 생성되고 콜 스택에 푸시되어 현재 실행 중인 실행 컨텍스트가 됩니다.
이후 foo 함수가 종료되어 콜 스택에서 pop됩니다.
결국 setTimeout의 콜백 함수는 태스크 큐에 푸시되어 대기하다가 콜 스택이 비면,
즉 전역 코드 및 명시적으로 호출된 함수가 모두 종료하면 콜 스택에 푸시되어 실행됩니다.
결국 자바스크립트 엔진은 싱글 스레드로 동작하지만 브라우저는 멀티 스레드로 동작하기 때문에 비동기로 동작할 수 있습니다.
너무 짧게 끝나서 뭐지 했어요. MDN도 그렇고 딥다이브도 그렇게 자세하게 이야기하지 않더라구요...
왜인가했더니 뒤에서 프로미스랑 async/await가 기다리고 있음^^
그때 봅시다.
MDN & 모던 자바스크립트 Deep Dive 내용을 참고하였습니다.
'내가 해냄 > JS' 카테고리의 다른 글
jsconfig.json 내가 해냄 (0) | 2023.03.25 |
---|---|
디스트럭처링 할당 내가 해냄 (4) | 2023.03.21 |
프로토타입 내가 해냄 (0) | 2023.03.15 |
배열 고차 함수 내가 해냄 (0) | 2023.03.14 |
ES6 함수 내가 해냄 (0) | 2023.03.14 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!