美文网首页
浅析Node的nextTick

浅析Node的nextTick

作者: 清_源_ | 来源:发表于2017-05-11 11:35 被阅读0次

    看thinkjs源码的时候发现下面这段代码。

      cluster.on('exit', worker => {
        think.log(new Error(think.locale('WORKER_DIED', worker.process.pid)), 'THINK');
        process.nextTick(() => cluster.fork());
      });
    

    这段代码的意思很简单,就是cluster挂了以后重新fork一个。
      但是注意到其中的process.nextTick(() => cluster.fork());这行,刚开始想了一下没有理解为什么不直接fork,后面仔细想了一下,发现如果直接fork,在fork的过程中又出现错误导致进程退出,而cluster又监听到exit的事件,就会不断的重复这个过程,阻塞Node进程。
      如果使用process.nextTick(() => cluster.fork());则不会阻塞Node的事件循环,只会在Event Loopclose callbacks阶段执行fork,即使程序一直fork失败也不会导致程序假死。(如果有疑问可以阅读文章末的扩展阅读)。
      下面的Demo说明了为什么使用了nextTick不会导致程序假死。

    var EventEmitter = require('events').EventEmitter; 
    var event = new EventEmitter();
    
    var count = 0;
    var num = 10;
    
    event.on('some_event', function() { 
      count++;
      console.log('some_event 事件触发' + count);
      if (count < num) {
        event.emit('some_event')
      }
    });
    
    event.emit('some_event'); 
    
    console.log('what ?')
    
    

    运行这段代码就会输出

    some_event 事件触发1
    some_event 事件触发2
    some_event 事件触发3
    some_event 事件触发4
    some_event 事件触发5
    some_event 事件触发6
    some_event 事件触发7
    some_event 事件触发8
    some_event 事件触发9
    some_event 事件触发10
    what ?
    

    可以发现 what ? 在最后才输出。如果把num设置的非常大就会报错

    internal/process/next_tick.js:148
        nextTickQueue.push({
                     ^
    RangeError: Maximum call stack size exceeded
    

    V8不断的向事件队列里添加任务,最终导致出现溢出,把event.emit('some_event')改写成

    process.nextTick(function(){ 
      event.emit('some_event') 
    });
    

    就会发现输出成了

    ome_event 事件触发1
    what ?
    some_event 事件触发2
    some_event 事件触发3
    some_event 事件触发4
    some_event 事件触发5
    some_event 事件触发6
    some_event 事件触发7
    some_event 事件触发8
    some_event 事件触发9
    some_event 事件触发10
    

    what ?并不会被阻塞,而且无论num改成多少,都不会出现栈溢出的错误。
      Node的Event loop执行流程如图

       ┌───────────────────────┐
    ┌─>│        timers         │
    │  └──────────┬────────────┘
    |      nextTick(队列执行)
    │  ┌──────────┴────────────┐
    │  │     I/O callbacks     │
    │  └──────────┬────────────┘
    |       nextTick(队列执行)
    │  ┌──────────┴────────────┐
    │  │     idle, prepare     │
    │  └──────────┬────────────┘      
    |      nextTick(队列执行)         ┌───────────────┐
    │  ┌──────────┴────────────┐      │   incoming:   │
    │  │         poll          │<─────┤  connections, │
    │  └──────────┬────────────┘      |               |
    |      nextTick(队列执行)         │   data, etc.  │
    │  ┌──────────┴────────────┐      └───────────────┘
    │  │        check          │
    │  └──────────┬────────────┘
    |       nextTick(队列执行)
    │  ┌──────────┴────────────┐
    └──┤    close callbacks    │
       └───────────────────────┘
    

    直接event.emit('some_event')的时候,Node不断的把收集到的事件塞到I/O callbacks这个队列,如果有大量的事件塞入就会最终导致溢出,就是上面的Maximum call stack size exceeded错误。
      如果加了process.nextTick则会不断的把emit的事件回调加到nextTickQueue队列,在各个主队列切换的时候执行,见上图的 nextTick(队列执行)。上面的那段Demo把event.emit('some_event')修改后的执行顺序就是
      1、发送事件
      2、把事件回调函数添加到nextTickQueue(注意,这个时候nextTickQueue队列里只有一个事件回调函数,如果当前队列尚未执行完毕并且没有发生切换,则nextTickQueue队列里的事件永远不会执行)
      3、执行nextTickQueue里的第一个事件回调(当前队列执行完毕或者执行到一定数量发生切换时,事件回调又会重新创建一个新的nextTickQueue队列并添加一个事件回调)
      4、然后同上
      这样就没有阻塞Node的事件循环,无论num多大都不会撑爆I/O callbacks队列。其实最核心的思想就是将任务拆解到若干次事件循环中,逐步执行。

    扩展阅读
      Node.js的event loop及timer/setImmediate/nextTick
      Node.js 原理简介
      深入理解Node.js:核心思想与源码分析

    相关文章

      网友评论

          本文标题:浅析Node的nextTick

          本文链接:https://www.haomeiwen.com/subject/fxcotxtx.html