이 기사에서는 nodejs의 이벤트를 설명하고 setTimeout, setImmediate 및 process.nextTick 간의 차이점에 대해 논의합니다. 도움이 필요한 친구들이 모두 참고할 수 있기를 바랍니다.
nodejs는 단일 스레드이지만 시스템 커널은 작업이 완료되면 시스템 커널에 작업을 위임할 수 있습니다. , 따라서 nodejs에서 콜백 메소드를 트리거합니다.
이러한 콜백은 라운드 로빈 대기열에 추가되어 결국 실행됩니다.
이 이벤트 루프 설계를 통해 nodejs는 마침내 비차단 IO를 달성할 수 있습니다. [관련 권장 사항: "nodejs Tutorial"]
nodejs의 이벤트 루프는 단계로 구분됩니다. 다음 그림은 각 단계의 실행 순서를 나열합니다.
각 단계는 콜백 대기열을 유지합니다. 이는 FIFO입니다. 대기줄.
단계에 들어가면 먼저 해당 단계의 작업을 실행한 다음 해당 단계에 속한 콜백 작업을 실행합니다.
이 콜백 대기열의 모든 작업이 실행되거나 최대 콜백 실행 횟수에 도달하면 다음 단계로 진입합니다.
Windows와 Linux의 구체적인 구현은 약간 다릅니다. 여기서는 가장 중요한 단계에만 중점을 둡니다.
질문: 단계 실행 중에 최대 콜백 실행 횟수를 제한해야 하는 이유는 무엇입니까?
답변: 극단적인 경우 특정 단계에서 많은 수의 콜백을 실행해야 할 수도 있습니다. 이러한 콜백을 실행하는 데 너무 많은 시간이 걸리면 nodejs의 작동이 차단되므로 콜백 수에 제한을 설정합니다. Nodejs 긴 블록을 피하기 위한 실행입니다.
위 사진에는 6단계가 나열되어 있으며, 다음에는 하나씩 설명드리겠습니다.
timers
타이머는 중국어로 타이머를 의미하며, 이는 주어진 시간이나 간격에 특정 콜백 기능을 실행한다는 의미입니다.
두 가지 일반적인 타이머 함수인 setTimeout과 setInterval이 있습니다.
일반적으로 이러한 콜백 함수는 만료 후 최대한 많이 실행되지만 다른 콜백 실행의 영향을 받습니다. 예를 살펴보겠습니다.
const fs = require('fs');function someAsyncOperation(callback) { // Assume this takes 95ms to complete fs.readFile('/path/to/file', callback);}const timeoutScheduled = Date.now();setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms have passed since I was scheduled`);}, 100);// do someAsyncOperation which takes 95 ms to completesomeAsyncOperation(() => { const startCallback = Date.now(); // do something that will take 10ms... while (Date.now() - startCallback < 10) { // do nothing }});
위 예에서는 someAsyncOperation을 호출했습니다. 이 함수는 먼저 readFile 메소드를 실행하기 위해 돌아갑니다. 이 메소드는 95ms가 소요됩니다. 그런 다음 readFile의 콜백 함수를 실행합니다. 이 콜백은 10ms 동안 실행됩니다. 마지막으로 돌아가서 setTimeout에서 콜백을 실행합니다.
위의 예에서는 setTimeout이 100ms 후에 실행되도록 지정되어 있지만 실제로는 95 + 10 = 105ms를 기다려야 실제로 실행됩니다.
보류 중인 콜백
이 단계에서는 일부 시스템 콜백 작업을 수행합니다. 예를 들어 TCP 연결을 만들 때 TCP 소켓은 ECONNREFUSED 신호를 수신합니다. 이 오류는 일부 Liunx 운영 체제에서 보고됩니다. 보류 중인 콜백에서 실행됩니다.
또는 다음 이벤트 루프에서 수행되어야 하는 I/O 콜백 작업.
idle, prepare
idle, prepare는 내부적으로 사용되는 단계이므로 여기서는 자세히 소개하지 않겠습니다.
poll polling
poll은 새로운 I/O 이벤트를 감지하고 I/O 관련 콜백을 실행합니다. 여기서 콜백은 닫기 콜백, 타이머 및 setImmediate 콜백 이벤트를 제외한 거의 모든 것을 나타냅니다.
poll은 주로 I/O 폴링, 블록 시간 계산, 폴 대기열의 이벤트 처리라는 두 가지 작업을 처리합니다.
폴링 대기열이 비어 있지 않으면 이벤트 루프는 대기열의 콜백을 순회한 다음 대기열이 소비되거나 콜백 수 제한에 도달할 때까지 하나씩 동기식으로 실행합니다.
큐에 있는 콜백들이 하나씩 동기적으로 실행되기 때문에 블로킹이 발생할 수 있습니다.
폴링 대기열이 비어 있고 코드에서 setImmediate가 호출되면 즉시 다음 확인 단계로 점프한 후 setImmediate에서 콜백을 실행합니다. setImmediate가 호출되지 않으면 새 콜백이 대기열에 추가되어 실행될 때까지 계속 기다립니다.
check
은 주로 setImmediate의 콜백을 실행하는 데 사용됩니다.
setImmediate는 별도의 단계에서 실행되는 고유 타이머로 볼 수 있으며 기본 libuv API는 콜백을 계획하는 데 사용됩니다.
일반적으로 poll 단계에 setImmediate라는 콜백이 있으면 poll 큐가 비면 즉시 poll 단계가 종료되고, 해당 콜백 메소드를 실행하기 위해 check 단계로 들어갑니다.
콜백 닫기
마지막 단계는 닫기 이벤트에서 콜백을 처리하는 것입니다. 예를 들어 소켓이 갑자기 닫히면 닫기 이벤트가 트리거되고 관련 콜백이 호출됩니다.
setTimeout和setImmediate有什么不同呢?
从上图的phase阶段可以看出,setTimeout中的callback是在timer phase中执行的,而setImmediate是在check阶段执行的。
从语义上讲,setTimeout指的是,在给定的时间之后运行某个callback。而setImmediate是在执行完当前loop中的 I/O操作之后,立马执行。
那么这两个方法的执行顺序上有什么区别呢?
下面我们举两个例子,第一个例子中两个方法都是在主模块中运行:
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
这样运行两个方法的执行顺序是不确定,因为可能受到其他执行程序的影响。
第二个例子是在I/O模块中运行这两个方法:
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
你会发现,在I/O模块中,setImmediate一定会在setTimeout之前执行。
两者的共同点
setTimeout和setImmediate两者都有一个返回值,我们可以通过这个返回值,来对timer进行clear操作:
const timeoutObj = setTimeout(() => { console.log('timeout beyond time'); }, 1500); const immediateObj = setImmediate(() => { console.log('immediately executing immediate'); }); const intervalObj = setInterval(() => { console.log('interviewing the interval'); }, 500); clearTimeout(timeoutObj); clearImmediate(immediateObj); clearInterval(intervalObj);
clear操作也可以clear intervalObj。
unref 和 ref
setTimeout和setInterval返回的对象都是Timeout对象。
如果这个timeout对象是最后要执行的timeout对象,那么可以使用unref方法来取消其执行,取消执行完毕,可以使用ref来恢复它的执行。
const timerObj = setTimeout(() => { console.log('will i run?'); }); timerObj.unref(); setImmediate(() => { timerObj.ref(); });
注意,如果有多个timeout对象,只有最后一个timeout对象的unref方法才会生效。
process.nextTick也是一种异步API,但是它和timer是不同的。
如果我们在一个phase中调用process.nextTick,那么nextTick中的callback会在这个phase完成,进入event loop的下一个phase之前完成。
这样做就会有一个问题,如果我们在process.nextTick中进行递归调用的话,这个phase将会被阻塞,影响event loop的正常执行。
那么,为什么我们还会有process.nextTick呢?
考虑下面的一个例子:
let bar; function someAsyncApiCall(callback) { callback(); } someAsyncApiCall(() => { console.log('bar', bar); // undefined }); bar = 1;
上面的例子中,我们定义了一个someAsyncApiCall方法,里面执行了传入的callback函数。
这个callback函数想要输出bar的值,但是bar的值是在someAsyncApiCall方法之后被赋值的。
这个例子最终会导致输出的bar值是undefined。
我们的本意是想让用户程序执行完毕之后,再调用callback,那么我们可以使用process.nextTick来对上面的例子进行改写:
let bar; function someAsyncApiCall(callback) { process.nextTick(callback); } someAsyncApiCall(() => { console.log('bar', bar); // 1 }); bar = 1;
我们再看一个实际中使用的例子:
const server = net.createServer(() => {}).listen(8080); server.on('listening', () => {});
上面的例子是最简单的nodejs创建web服务。
上面的例子有什么问题呢?listen(8000) 方法将会立马绑定8000端口。但是这个时候,server的listening事件绑定代码还没有执行。
这里实际上就用到了process.nextTick技术,从而不管我们在什么地方绑定listening事件,都可以监听到listen事件。
process.nextTick 和 setImmediate 的区别
process.nextTick 是立马在当前phase执行callback,而setImmediate是在check阶段执行callback。
所以process.nextTick要比setImmediate的执行顺序优先。
实际上,process.nextTick和setImmediate的语义应该进行互换。因为process.nextTick表示的才是immediate,而setImmediate表示的是next tick。
本文作者:flydean程序那些事
本文链接:http://www.flydean.com/nodejs-event-more/
本文来源:flydean的博客
更多编程相关知识,请访问:编程视频!!
위 내용은 nodejs의 이벤트 및 이벤트 루프에 대한 심층 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!