- 싱글 스레드 논 블로킹 이라고 한다.
- 하나의 스레드로 동작하지만 I/O 작업이 발생한 경우 이를 비동기적으로 처리할 수 있다.
- 스레드는 하나의 실행 흐름만을 가지고 있고 파일 읽기 와 같이 기다려야 하는 작업을 실행하면 그 작업이 끝나기 전에는 아무것도 할 수 없어야만 한다.
- Node.js
- Node.js Core Library
- Node.js Bindings
- V8 Engine
- libuv
nodejs -> (비동기 요청) -> libuv -> (커널에게 비동기 처리해달라고 요청) -> kenner
- 만약 커널이 비동기 처리를 할수 없는 작업이라면 libuv가 내부적으로 가지고 있는 스레드 풀에게 작업을 요청한뒤 응답한다
kernel -> (처리 후 응답) -> libuv -> (비동기 응답) -> nodejs
위와 같은 방식에서 nodejs는 libuv와 통신을 하기 때문에 단순히 비동기 요청 언어라고 봐도 무방 실제 작업을 하는 것은 libuv의 스레드 이다 기본값으로는 libuv가 4개의 스레드 풀을 생성한다고 하니 nodejs의 작업 능률은 멀티스레드와 같을 것이다..
- 타이머 페이즈
- setTimeout
- i/o 콜백 페이즈
- idle, prepare 페이즈
- poll 페이즈
- i/o 콜백관련
- check 페이즈
- close 페이즈
리드 파일 함수의 이벤트 루프
fs.readFile(path,(data, error)=>{
console.log(data);
});
- 기능에 대한 실행을 백그라운드로 넘긴다.
- readFile의 작업이 완료되면 그에대한 콜백이 Poll페이즈의 큐에 추가가 되고
- nodejs가 이벤트 페이즈 들을 돌아다니면서 큐에 있는 콜백들을 실행 시킨다
- nodejs 는 실행하기 전에 이벤트루프를 생성한다.
- 이벤트 루프 바깥에서 코드를 처음부터 끝까지 실행한다
- 그중에 비동기 작업들은 이벤트루프의 페이즈에 큐에 담는다
- 이벤트루프의 작업이 없으면 exit callbacks 를 실행하고 프로그램을 종료한다
- 이벤트 루프의 작업이 있으면 페이즈를 차례로 돌면서 실행할 수 있는 작업을 실행합니다.
- 1회전 마다 이벤트 루프가 살아있는지 확인 하고 작업이 없으면 exit callbacks를 실행하고 프로그램을 종료한다.
nodejs는 이벤트루프에서 각 페이즈가 관리하는 작업만 실행할 수 있다 그래서 페이즈를 순회하면서 Timer 페이즈에 진입해야만 timer 를 실행할 기회를 얻기 떄문에 1ms의 setTimeout 을 실행 해도 1ms뒤에 함수가 실행됨을 보장하지 않는다
Timer페이즈는 min-heap을 이용해 최솟값을 찾는 방식으로 타이머를 관리한다. 오름 차순으로 관리하기에 조건에 맞을때 마다 하나씩 꺼내서 실행하며 맞지 않으면 바로 다음페이즈로 넘어간다
실행한도에 다다르면 다음 실행할수 있는 타이머가 남아있더라도 다음페이즈로 넘어간다
타이머 작업, 다른 비동기작업의 대기가 없다면 무한정 대기한다...? 이건 좀 더 알아봐야함 다른 작업이 있으면 timeout = 0 대기시간이 존재(timeout) 남아있는 타이머가 잇는데 아직 대기해야 하는 시간이 있다면? 그시간동안 timeout = 남은 시간
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(()=>{
console.log('timeout')
},0)
setImmediate(()=>{
console.log('immediate');
})
});
setTimeout(()=>{
console.log('timeout')
},0)
setImmediate(()=>{
console.log('immediate');
})
결과는? fs 외부 timeout immediate fs 내부 immediate timeout
- 현재 실행 중인 작업이 완료되었을 때
• setTimeout과 setImmediate는 일반적으로 setTimeout이 timers 단계에서 실행되고 setImmediate가 check 단계에서 실행되기 때문에 setTimeout이 더 먼저 실행됩니다.
- I/O 작업이 완료된 후
• I/O 작업이 완료된 후에는 setImmediate 콜백이 setTimeout 콜백보다 먼저 실행되는 경향이 있습니다. 이는 I/O 콜백이 완료된 후 이벤트 루프가 check 단계를 먼저 처리하기 때문입니다.
setTimeout(() => {
console.log(1)
process.nextTick(() => {
console.log(3)
})
Promise.resolve().then(() => console.log(4))
}, 0)
setTimeout(() => {
console.log(2)
}, 0)
우선순위
- nextTick = nextTickQueue
- resolve = microTaskQueue
- callback
결과 1 3 4 2
우선순위가 높은 nextTick 과 microTask의 큐가 비어있지 않으면 다음 페이즈로 진입하지 않는다 즉 재귀가 발생하면 영원히 다음 페이즈로 진입하지 않아 console2의 setTimeout 은 실행되지 않음
위 사진을 보면 알 수 있듯이 Node.js
는 비동기 처리를 libuv
에게 위임하는데 libuv
는 내부적으로 여러 Phase
를 나누어서 비동기 처리를 하고 이 방법이 이벤트 루프라고 생각하면 될 것 같습니다.