この記事では、nodejs のイベントについて説明し、setTimeout、setImmediate、process.nextTick の違いについて説明します。一定の参考値があるので、困っている友達が参考になれば幸いです。
nodejs はシングルスレッドですが、nodejs は操作できます。これらのタスクをバックグラウンドで処理するシステム カーネル。タスクが完了すると、nodejs に通知し、nodejs のコールバック メソッドをトリガーします。
これらのコールバックはラウンド ロビン キューに追加され、最終的に実行されます。
このイベント ループ設計を通じて、nodejs は最終的にノンブロッキング IO を実現できます。 [関連する推奨事項: "nodejs チュートリアル "]
nodejs のイベント ループはフェーズに分割されています。次の図は、各フェーズの実行順序を示しています。
各フェーズは、FIFO キューであるコールバック キューを維持します。
フェーズに入ると、最初にフェーズのタスクが実行され、次にフェーズに属するコールバック タスクが実行されます。
このコールバック キュー内のすべてのタスクが実行されるか、コールバック実行の最大数に達すると、次のフェーズに入ります。
Windows と Linux の具体的な実装は若干異なることに注意してください。ここでは、最も重要なフェーズのみに焦点を当てます。
質問: フェーズの実行中に、コールバック実行の最大数を制限する必要があるのはなぜですか?
回答: 極端なケースでは、特定のフェーズで多数のコールバックを実行する必要がある場合があります。これらのコールバックの実行に時間がかかりすぎると、nodejs の動作がブロックされるため、コールバックの数を設定します。コールバックの実行。nodejs の長いブロックを避けるために制限します。
上の図には 6 つのフェーズがリストされています。次に、それらを 1 つずつ説明します。
タイマー
タイマーの中国語の意味はタイマーであり、特定の時間または間隔で特定のコールバック関数を実行することを意味します。
一般的なタイマー関数として、setTimeout と setInterval の 2 つがあります。
一般に、これらのコールバック関数は有効期限が切れた後も可能な限り実行されますが、他のコールバックの実行の影響を受けます。例を見てみましょう:
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 メソッドの実行に戻ります。このメソッドには 95 ミリ秒かかると仮定します。次に、readFile のコールバック関数を実行します。このコールバックは 10 ミリ秒間実行されます。最後に、戻って setTimeout でコールバックを実行します。
したがって、上記の例では、setTimeout は 100 ミリ秒後に実行されるように指定されていますが、実際には、実際に実行されるまで 95 10 = 105 ミリ秒待つ必要があります。
保留中のコールバック
このフェーズでは、いくつかのシステム コールバック操作が実行されます。たとえば、TCP 接続を確立するとき、TCP ソケットは ECONNREFUSED シグナルを受け取ります。一部の liunx では、これオペレーティング システムでエラーが報告され、このシステムのコールバックが保留中のコールバックで実行されます。
または、次のイベント ループで実行する必要がある I/O コールバック操作。
idle, prepare
idle, prepare は内部的に使用されるフェーズなので、ここでは詳しく紹介しません。
ポーリング ポーリング
ポーリングは、新しい I/O イベントを検出し、I/O 関連のコールバックを実行します。ここでのコールバックは、「ほぼすべてをオフにする」を除くすべてを指します。コールバック、タイマー、setImmediate を除くコールバック イベント。
poll は主に 2 つのことを処理します。I/O のポーリング、ブロック時間の計算、そしてポーリング キュー内のイベントの処理です。
ポーリング キューが空でない場合、イベント ループはキュー内のコールバックを走査し、キューが消費されるかコールバック数の制限に達するまで、それらを 1 つずつ同期的に実行します。
キュー内のコールバックは 1 つずつ同期して実行されるため、ブロッキングが発生する可能性があります。
ポーリング キューが空で、コード内で setImmediate が呼び出された場合、すぐに次のチェック フェーズにジャンプし、setImmediate でコールバックを実行します。 setImmediate が呼び出されない場合、新しいコールバックがキューに追加されて実行されるまで待機し続けます。
check
主に setImmediate のコールバックを実行するために使用されます。
setImmediate は、別のフェーズで実行される固有のタイマーとして見ることができ、基礎となる libuv API を使用してコールバックを計画します。
一般的に、ポーリング フェーズに setImmediate というコールバックがある場合、ポーリング キューが空になるとすぐにポーリング フェーズが終了し、対応するコールバック メソッドを実行するためにチェック フェーズに入ります。
close コールバック
最後のフェーズでは、close イベントのコールバックを処理します。たとえば、ソケットが突然閉じられると、close イベントがトリガーされ、関連するコールバックが呼び出されます。
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 中国語 Web サイトの他の関連記事を参照してください。