개요
JavaScript를 사용하다 보면 Single-Threaded 나 Event Loop 에 대한 내용을 자주 접하게 된다.
여러 글을 읽다가 한번 정리해야겠다는 생각이 들어서 정리해보려고 한다.
시작하기 전에
아래 코드를 실행하면 결과가 어떻게 출력될 지 생각해보자.
console.log('Main 1');
const interval = setInterval(() => {
console.log('interval 1');
}, 0);
setTimeout(() => {
console.log('timeout 1');
Promise.resolve()
.then(() => {
console.log('promise 1');
})
.then(() => {
console.log('promise 2');
clearInterval(interval);
});
console.log('timeout 2');
})
console.log('Main 2');
Promise.resolve()
.then(() => {
console.log('promise 3');
})
.then(() => {
console.log('promise 4');
});
JavaScript
복사
JavaScript Engine
자바스크립트 엔진은 Memory Heap 과 Call Stack 으로 구성되어 있다.
JavaScript는 Single-Threaded 이다. ( Call Stack을 1개만 사용한다 )
싱글 스레드는 한 번에 한 개의 작업만 실행할 수 있다.
이러한 특징 때문에 자바스크립트의 함수가 동작하는 방식을 Run to Completion이라고 한다.
일반적으로는 한 번에 한 작업만 실행할 수 있는 게 큰 문제가 되지 않지만, 한 개의 작업이 오래 걸리면 뒤에 있는 다른 작업이 실행이 안 되고 계속 지연된다.
•
Memory Heap: 동적으로 생성된 객체 ( 인스턴스 ) 가 저장되는 영역
•
Call Stack: 코드가 실행될 때 쌓이는 영역 ( Stack 형태 )
Task Queue
Event Queue == Task Queue == Message Queue ( 모두 같은 말 )
NodeJS 는 2개의 Queue( Macro Task, Micro Task )를 사용한다.
Macro Task를 1개 수행한 후 Micro Task Queue에 있는 모든 Task를 수행한다. ( 혹은 지정된 Max Count까지 수행한다. )
•
Macro Task Queue
◦
I/O
◦
setInterval
◦
setImmediate
◦
setTimeout
•
Micro Task Queue
◦
process.nextTick
◦
Promise
◦
Object.observe
Event Loop
이벤트 루프는 가능하면 언제나 시스템 커널에 Task를 넘겨서 Node.js 가 Non Blocking I/O 작업을 수행하도록 한다.
대부분의 커널은 Multi-Thread이기 때문에 백그라운드에서 여러 개의 Task를 수행할 수 있다. 이러한 작업 중 하나가 완료되면 커널이 Node.js 에게 알려줘서 Callback을 Poll Queue에 추가할 수 있도록 한다.
Event Queue는 총 7개가 있으며, 각 Queue를 Phase ( 단계 )라고 표현한다.
이벤트 루프가 실행하는 사이 Node.js 는 다른 비동기 I/O 나 Timer를 기다리고 있는지 확인하고, 없으면 종료한다.
각 Phase 들은 Callback의 FIFO Queue를 가지고 있다.
Timers
•
setTimeout, setInterval Callback 이 실행되는 단계
•
Timer Phase에 Task를 넣는 것은 Poll Phase에서 실행
•
Timer Phase는 단지 시간 관련 함수를 실행
Pending Callbacks
•
Loop의 반복으로 연기된 I/O Callback 들을 실행
idle/prepare
•
내부용으로만 사용
Poll
•
I/O 를 얼마나 오래 Block 해야 하고 Polling 해야 하는지 계산함
•
Poll Queue에 있는 이벤트를 처리함
•
Scheduling 된 타이머가 없으면
1.
Poll Queue가 비어있지 않다면 이벤트 루프가 Queue를 처리하면서 다 처리하거나 최대치에 도달할 때까지 동기적으로 Callback을 실행함
2.
Poll Queue가 비어있다면
•
setImmediate 가 스케줄링 되어 있다면 Poll Phase를 종료하고 Check Phase 로 넘어감
•
setImmediate 가 스케줄링 되어 있지 않으면 Callback이 Queue에 추가될 때까지 기다린 후 즉시 실행함
Check
•
setImmediate Callback 이 실행되는 단계
•
Poll 실행 직후 바로 실행됨
Close
•
일부 Close Callback 들이 실행되는 단계
•
socket.close 등
연습 문제
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
JavaScript
복사
출력 결과는?
1.
timeout → immediate
2.
immediate → timeout
3.
알 수 없다.
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
JavaScript
복사
출력 결과는?
1.
timeout → immediate
2.
immediate → timeout
3.
알 수 없다.