nodejsイベントサイクルイベントループを徹底的に理解する


Node.jsが起動するとイベントloopが初期化されます.各イベントloopは以下の順序で6つの循環段階が含まれます.
   ┌───────────────────────┐
┌─>│        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とsetImmedite
setTimeout(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        1msloop->time >=1 uv_run_timertimeout   
2.     loop        1msloop->time < 1loop     uv_run_timerio_poll    uv_run_checkimmediateclose cbuv_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