nodejsイベントサイクルイベントループを徹底的に理解する
14602 ワード
Node.jsが起動するとイベントloopが初期化されます.各イベントloopは以下の順序で6つの循環段階が含まれます.
私たちの普段の非同期ioはどの段階で行われていますか?答えはpoll段階です.
poll段階
node.jsの中で、上のいくつかの特定の段階のcalbackを除いて、いかなる非同期の方法が完成する時、そのcalbackをpoll queueの中に入れます.
イベントloopがpoll段階に達すると、timerが存在しなくなり、次のような状況が発生します.
もしpoll queueが空でないならば、event loopは同時にqueueの中のcalbackを実行して、queueが空であるまで、あるいは実行するcalbackはシステムの上限に達します.poll queueが空であれば、次のような状況が発生します.
コードがセットされている場合、calbackまたはclose calbacks段階を満たすcalbackが設定されています.event loopは、poll段階を終了してcheck段階に入り、check段階のqueueを実行します.
コードがセットされていない場合、またはclose calbacks段階を満たしていないcalback、event loopはこの段階でcalbacksがpoll queueに加入するのを待つことになります.イベントloopがpoll段階に達すると、timerがあり、timerがタイムアウト時間にならない場合、次のような状況が発生します.
一番近いタイムアウト時間をパラメータとしてオウに伝えます.poll()において、このようなイベントloopがpoll段階でブロックされている場合、I/Oイベントのトリガがなければ、timerutによって起動待ちの操作がトリガされ、この段階を終了し、その後、close calbacks段階が終了したら、一回のタイムアウト判定実際には、timer検査は2つの場所で行われます.timers段階とclose calbacks段階が終わった後です.
他の人のコメントのソースコードを貼り付けます.
nodeにおいて、setTimeout(cb,0)==setTimeout(cb,1);setImmedia telyはuv_に属します.run_checkの部分は確かにloopが入るたびにuv_をチェックします.run_timerのですが、cpuの作業に時間がかかります.例えば、初めて取得したhrtimeが0であれば、setTimeout(cb、1)、タイムアウト時間はloop->time=1(ms、nodeタイマーが1 msまで正確ですが、hrtimeはナノ秒まで正確です.)ですので、初めてloopが入った時は2つのケースがあります.
Process.nexTick()
process.nextTick()は、event loopの任意の段階ではなく、各段階で切り替えられた中間で実行され、すなわち、1段階から次の段階に切り替える前に実行される.
process.nextTick()はnodeの初期バージョンがsetImmediateがない時の産物で、nodeの作者は私達ができるだけsetImmediteを使うことを推薦します.
参考記事:
https://cnodejs.org/topic/57d68794cb6f605d360105bf
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#i-o-calbacks
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
timers段階: この段階でsetTimeout(calback)とset Interval(calback)で予約されたcalbackを実行します.I/O calbacks段階: This phase executes calbacks for some system operation s such as types of TCP errors.For example if a TCP socket receives ECONNREFUSED
when atempting to connect,some*nix system want to report the error.This will be queued to execute in the I/O calbacks phase;IDle,prepare段階: nodeの内部のみ使用します.poll段階: 新しいI/Oイベントを取得すると、適切な条件でnodeがここにブロックされます.チェック段階: setImmediate()設定を実行するコールバック.close calbacks段階: 例えば、socket.onのcalbackはこの段階で実行されます.イベントloopは上の六つの段階を順次実行します.各段階にcalbacksが入っているfifo queueがあります.イベントloopが指定の段階に実行されると、nodeはこの段階のfifo queueを実行します.キューcalbackが実行されるか、またはcalbacksの数量がこの段階の上限を超えたら、イベントは次の段階に移ります.私たちの普段の非同期ioはどの段階で行われていますか?答えはpoll段階です.
poll段階
node.jsの中で、上のいくつかの特定の段階のcalbackを除いて、いかなる非同期の方法が完成する時、そのcalbackをpoll queueの中に入れます.
イベントloopがpoll段階に達すると、timerが存在しなくなり、次のような状況が発生します.
もしpoll queueが空でないならば、event loopは同時にqueueの中のcalbackを実行して、queueが空であるまで、あるいは実行するcalbackはシステムの上限に達します.poll queueが空であれば、次のような状況が発生します.
コードがセットされている場合、calbackまたはclose calbacks段階を満たすcalbackが設定されています.event loopは、poll段階を終了してcheck段階に入り、check段階のqueueを実行します.
コードがセットされていない場合、またはclose calbacks段階を満たしていないcalback、event loopはこの段階でcalbacksがpoll queueに加入するのを待つことになります.イベントloopがpoll段階に達すると、timerがあり、timerがタイムアウト時間にならない場合、次のような状況が発生します.
一番近いタイムアウト時間をパラメータとしてオウに伝えます.poll()において、このようなイベントloopがpoll段階でブロックされている場合、I/Oイベントのトリガがなければ、timerutによって起動待ちの操作がトリガされ、この段階を終了し、その後、close calbacks段階が終了したら、一回のタイムアウト判定実際には、timer検査は2つの場所で行われます.timers段階とclose calbacks段階が終わった後です.
他の人のコメントのソースコードを貼り付けます.
//deps/uv/src/unix/core.c
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
//uv__loop_alive event loop handle request
// closing_handles NULL, , 0
r = uv__loop_alive(loop);
// event loop , ms
if (!r)
uv__update_time(loop);
while (r != 0 && loop->stop_flag == 0) {
// Linux Timer hrtime loop->time, event loop
uv__update_time(loop);
// loop->time Timer, loop timer
uv__run_timers(loop);
// pending_queue , &loop->pending_queue uv__io_t cb
ran_pending = uv__run_pending(loop);
// loop-watcher.c , &loop->idle_handles idle_cd ( )
uv__run_idle(loop);
// loop-watcher.c , &loop->prepare_handles prepare_cb ( )
uv__run_prepare(loop);
timeout = 0;
// UV_RUN_ONCE , pending_queue , UV_RUN_DEFAULT( loop ), timeout
// , uv_io_poll timer
if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop);
// I/O ( ), timeout uv_io_poll timers; mode
//UV_RUN_NOWAIT uv_run ,timeout 0 uv__io_poll,
uv__io_poll(loop, timeout);
// loop-watcher.c , &loop->check_handles check_cb ( )
uv__run_check(loop);
// ,loop->closing_handles NULL
uv__run_closing_handles(loop);
if (mode == UV_RUN_ONCE) {
// UV_RUN_ONCE , event loop
uv__update_time(loop);
// timers, timer
uv__run_timers(loop);
}
r = uv__loop_alive(loop);
// UV_RUN_ONCE UV_RUN_NOWAIT ,
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break;
}
// stop_flag 0, loop
if (loop->stop_flag != 0)
loop->stop_flag = 0;
// r
return r;
}
set TimeoutとsetImmeditesetTimeout(function timeout () {
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
上記二者の実行順序は不確定です.nodeにおいて、setTimeout(cb,0)==setTimeout(cb,1);setImmedia telyはuv_に属します.run_checkの部分は確かにloopが入るたびにuv_をチェックします.run_timerのですが、cpuの作業に時間がかかります.例えば、初めて取得したhrtimeが0であれば、setTimeout(cb、1)、タイムアウト時間はloop->time=1(ms、nodeタイマーが1 msまで正確ですが、hrtimeはナノ秒まで正確です.)ですので、初めてloopが入った時は2つのケースがあります.
1. loop 1ms, loop->time >=1 , uv_run_timer ,timeout
2. loop 1ms, loop->time < 1, loop uv_run_timer , io_poll uv_run_check, immediate , close cb , uv_run_timer
但し、両者が非同期i/o calback内部で呼び出した場合は、常にset Immediateを実行し、setTimeoutを実行する.var fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout')
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
fs.readFile calbackが実行された後、プログラムはtimerとsetImmediateを設定しているので、poll段階はブロックされず、さらにcheck段階に進んでsetImmediteを実行し、最後にclose calbacks段階が終わったらtimerを確認し、timeoutイベントを実行します.Process.nexTick()
process.nextTick()は、event loopの任意の段階ではなく、各段階で切り替えられた中間で実行され、すなわち、1段階から次の段階に切り替える前に実行される.
var fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
process.nextTick(()=>{
console.log('nextTick3');
})
});
process.nextTick(()=>{
console.log('nextTick1');
})
process.nextTick(()=>{
console.log('nextTick2');
})
});
実行結果:nextTick1
nextTick2
setImmediate
nextTick3
setTimeout
poll->check段階から、まずprocess.nextTick、nextTick 1、nextTick 2を実行します.そしてcheckに入ってsetImmediteを実行し、setImmediteを実行した後、checkを出して、close calbackに入る前に、process.nextTickを実行します.nextTick 3.最後にtimerに入ってsetTimeoutを実行します.process.nextTick()はnodeの初期バージョンがsetImmediateがない時の産物で、nodeの作者は私達ができるだけsetImmediteを使うことを推薦します.
参考記事:
https://cnodejs.org/topic/57d68794cb6f605d360105bf
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#i-o-calbacks