1
- 초기 컴퓨터 프로그램은 한 번에 하나의 작업만 처리
- 입출력 기다리는 동안 CPU는 놀고 있음
- 구조는 단순하지만 효율은 극악
2
- 운영체제는 언제 올지 모르는 이벤트를 기다려야 함(키보드, 마우스 입력 등)
- 계속 기다리지 않고 이벤트가 발생하면 처리하는 이벤트 기반 아키텍처 등장
- GUI 시대의 등장으로 이벤트 루프는 필수가 됨
- 이벤트 루프(Event Loop)는 블로킹 문제를 해결하기 위한 메커니즘이라고 할 수 있음
- 이벤트 루프는 실행 가능한 작업이 생길 때까지 기다렸다가 하나를 선택해 실행하는 기본적인 프로그램 구조
- 항상 그렇진 않지만 즉시 실행할 수 없는 작업을 큐로 관리하는 것이 일반적
- 실행 가능한 순간마다 하나씩 처리하는 것이 일반적
3
- 웹 서버의 시대가 되면서 동시 접속이 많아지면 싱글 스레드로는 역부족
- 이를 해결하기 위한 멀티스레딩과 비동기 I/O + 이벤트 루프 등장
- 이벤트 루프는 동시성(concurrency)를 해결하고 멀티스레드는 병렬성(parallelism)을 해결
- 웹 서버 요청의 본질은 파일, DB 밑 네트워크의 기다림이기 때문에 비동기 I/O + 이벤트루프가 각광
4
- Javascript는 싱글 스레드
- 스레드 하나는 스레드 여러 개에 비해 안정적임
- 특히 UI를 안정적으로 표현하는데 유리함
- 멀티 스레드가 접근 가능한 DOM을 상상해보면 UI 안정성이 크게 낮아질 것
- Web Worker 또한 같은 이유로 DOM에 접근 불가
- 싱글 스레드와 이벤트 루프 조합은 예측 가능하고 디버깅이 상대적으로 쉬움
- 게다가 JS가 들어오기 전부터 브라우저는 이미 이벤트 루프 구조였다고 함
5
- Javascript로 실행한 코드는 콜 스택에 쌓이고 순차적으로 처리됨
setTimout, DOM 이벤트 콜백 등은 먼저 태스크 큐(Task Queue)에 쌓임- 이벤트 루프는 콜 스택이 비워질 때마다 큐에 있는 콜백을 하나씩 스택에 옮겨 실행
Promise의then과 같은 작업은 마이크로 태스크 큐(MicroTask Queue)에 쌓여 우선순위 높게 처리
6
- microtask queue는 렌더링 전에 모두 비워지기 때문에 무거운 작업은 넣지 말아야 함
- 만약 거대한 loop logic을 넣는다면 큐가 오랜 시간 비워지지 않아서 탭이 먹통될 수 있음
- microtask의 목적은 상태 일관성, 렌더링 안정성이어야 함
- 어울리는 작업은 상태 후처리, 로깅, 플래그 정리
- 어울리지 않는 작업은 반복 계산, 애니메이션, 폴링
- 무거운 작업은 macrotask에 양보하거나,
requestAnimiationFrame,Worker등을 활용
참고
console.log('a');
(async () => {
console.log('a-b');
setTimeout(() => console.log('t'), 1);
await new Promise((resolve) => { resolve() });
console.log('b');
})();
requestAnimationFrame(() => new Promise((res) => { res(); }).then(() => console.log('rt')));
requestAnimationFrame(() => requestAnimationFrame(() => new Promise((res) => { res(); }).then(() => console.log('rt2'))));
(async () => {
await new Promise((resolve) => { resolve() });
requestAnimationFrame(() => console.log('rt3'));
})();
requestAnimationFrame(() => setTimeout(() => console.log('t2'), 1));
console.log('c');