美文网首页Node.js
Nodejs 解读event loop的事件处理机制

Nodejs 解读event loop的事件处理机制

作者: 编程go | 来源:发表于2017-09-03 18:58 被阅读1939次

    摘要:

    1. nodejs 为什么要存在一个event loop的事件处理机制?
    2. event loop的事件处理机制如何运作的?
    3. 从event loop机制的角度上区分setImmediate()与setTimeout()
    4. 从event loop机制的角度上区分process.nextTick()与setImmediate()

    noted:内容来源对nodejs 官方文档学习总结

    1. nodejs 为什么要存在一个event loop的事件处理机制?

    nodejs 具有事件驱动和非阻塞但线程的特点,使相关应用变得比较轻量和高效。当应用程序需要相关I/O操作时,线程并不会阻塞,而是把I/O操作移交给底层类库(如:libuv)。此时nodejs线程会去处理其他的任务,当底层库处理完相关的I/O操作后,会将主动权再次交还给nodejs线程。因此event loop的作用就是起到调度线程的作用,如当底层类库处理I/O操作后调度nodejs单线程处理后续的工作。也就是说当nodejs 程序启动的时候,它会开启一个event loop以实现异步的api调度、schedule timers 、回调process.nextTick()。

    从上也可以看出nodejs 虽说是单线程,但是在底层类库处理异步操作的时候仍然是多线程。

    2. event loop的事件处理机制如果运作的?

    eventloop.jpg

    上述的五个阶段都是按照先进先出的规则执行回调函数。按顺序执行每个阶段的回调函数队列,直至队列为空或是该阶段执行的回调函数达到该阶段所允许一次执行回调函数的最大限制后,才会将操作权移交给下一阶段。

    每个阶段的简单概要:

    • timers: 执行setTimeout() 和 setInterval() 预先设定的回调函数。
    • I/O callbacks: 大部分执行都是timers 阶段或是setImmediate() 预先设定的并且出现异常的回调函数事件。
    • idle, prepare: nodejs 内部函数调用。
    • poll: 搜寻I/O事件,nodejs进程在这个阶段会选择在该阶段适当的阻塞一段时间。
    • check: setImmediate() 函数会在这个阶段执行。
    • close callbacks: 执行一些诸如关闭事件的回调函数,如socket.on('close', ...) 。

    每个阶段的详细内容:

    • poll
      该阶段主要是两个任务:
      1. 当timers 的定时器到时后,执行定时器(setTimeout 和 setInternal)的回调函数。
      2. 执行poll 队列里面的I/O 队列。
    poll phrase.png

    Noted:在poll phrase一旦event loop中的poll queue队列为空,poll 就会去timers 查看有没有到期的定时期需要执行。如果有,就会返回timer执行相应的回调函数。

    • timers
      指定线程执行定时器(setTimeout和 setInterval)的回调函数,但是大多数的时候定时器的回调函数执行的时间要远大于定时器设定的时间。因为必须要等poll phrase中的poll queue队列为空时,poll才会去查看timer中有没有到期的定时器然后去执行定时器中的回调函数。
    const fs = require('fs');
    
    function someAsyncOperation(callback) {
      // Assume this takes 95ms to complete
      fs.readFile('/Users/spursy/Develop/TFDemo/activateTF.txt', callback);
    }
    
    const timeoutScheduled = Date.now();
    
    setTimeout(function() {
    
      const delay = Date.now() - timeoutScheduled;
    
      console.log(delay + 'ms have passed since I was scheduled');
    }, 100);
    
    
    // do someAsyncOperation which takes 94 ms to complete
    someAsyncOperation(function() {
    
      const startCallback = Date.now();
    
      // do something that will take 10ms...
      while (Date.now() - startCallback < 10) {
        // do nothing
      }
    
    });
    

    上面的函数执行的结果是:

    104ms have passed since I was scheduled

    定时器加入到timer中,定时的时间设置为100秒。poll中执行的I/O操作,由于读取相应目录下的文件要耗费一些时间,poll将会阻塞在这里循环相应的回调函数,大约在94秒时相应的I/O操作执行完毕,对应的回调函数又耗费了10秒钟。这时poll queue为空,此时poll会去timer查看有没有到期的定时器。发现存在一个已经超时近4秒的定时器然后就执行定时器对应的回调函数,这样就是定时器执行了将近104秒钟时间的原因。

    • I/O callbacks
      该阶段执行一些诸如TCP的errors回调函数。
    • check
      如果poll中已没有排队的队列,并且存在setImmediate() 立即执行的回调函数,这是event loop不会在poll阶段阻塞等待相应的I/O事件,而是直接去check阶段执行setImmediate() 函数。
    • close callback
      该阶段执行close的事件函数。

    3. 从event loop机制的角度上区分setImmediate()与setTimeout()

    从Issue 2中poll和check阶段的逻辑,我们可以看出setImmediate和setTimeout、setInterval都是在poll 阶段执行完当前的I/O队列中相应的回调函数后触发的。但是这两个函数却是由不同的路径触发的。

    setImmediate函数,是在当前的poll queue对列执行后为空或是执行的数目达到上限后,event loop直接调入check阶段执行setImmediate函数。
    setTimeout、setInterval则是在当前的poll queue对列执行后为空或是执行的数目达到上限后,event loop去timers检查是否存在已经到期的定时器,如果存在直接执行相应的回调函数。

    如果程序中既有setTimeout和setImmediate,两者的执行顺序是什么?

    // timeout_vs_immediate.js
    setTimeout(function timeout() {
      console.log('timeout');
    }, 0);
    
    setImmediate(function immediate() {
      console.log('immediate');
    });
    

    上面的程序执行的结果并不是唯一的,有时immediate在前,有时timeout在qian。主要是由于他们运行的当前上下文环境中存在其他的程序影响了他们执行顺序。

    // timeout_vs_immediate.js
    const fs = require('fs');
    
    fs.readFile(__filename, () => {
      setTimeout(() => {
        console.log('timeout');
      }, 0);
      setImmediate(() => {
        console.log('immediate');
      });
    });
    

    上面的程序把setImmediate和setTimeout放到了I/O循环中,此时他们的执行顺序永远都是immediate在前,timeout在后。

    4. 从event loop机制的角度上区分process.nextTick()与setImmediate()

    1. process.nextTick()函数

    • 尽管process.nextTick()也是一个异步的函数,但是它并没有出现在上面event loop的结构图中。不管当前正在event loop的哪个阶段,在当前阶段执行完毕后,跳入下个阶段前的瞬间执行process.nextTick()函数。
    • 由于process.nextTick()函数的特性,很可能出现一种恶劣的情形:在event loop进入poll前调用该函数,就会阻止程序进入poll阶段allows you to "starve" your I/O by making recursive process.nextTick() calls
    • 但是也正是nodejs的一个设计哲学:每个函数都可以是异步的,即使它不必这样做。例如下面程序片段,如果不对内部函数作异步处理就可能出现异常。
    let bar;
    
    // this has an asynchronous signature, but calls callback synchronously
    function someAsyncApiCall(callback) { callback(); }
    
    // the callback is called before `someAsyncApiCall` completes.
    someAsyncApiCall(() => {
    
      // since someAsyncApiCall has completed, bar hasn't been assigned any value
      console.log('bar', bar); // undefined
    
    });
    
    bar = 1;
    

    由于someAsyncApiCall函数在执行时,内部函数是同步的,这是变量bar还没有被赋值。如果改为下面的就会这个异常。

    let bar;
    
    function someAsyncApiCall(callback) {
      process.nextTick(callback);
    }
    
    someAsyncApiCall(() => {
      console.log('bar', bar); // 1
    });
    
    bar = 1;
    

    2. 两者的比较

    • process.nextTick() 函数是在任何阶段执行结束的时刻
    • setImmediate() 函数是在poll阶段后进去check阶段事执行

    3. process.nextTick() 函数的应用

    • 允许线程在进入event loop下一个阶段前做一些关于处理异常、清理一些无用或无关的资源。l例如下面:
    function apiCall(arg, callback) {
      if (typeof arg !== 'string')
        return process.nextTick(callback,
                                new TypeError('argument should be string'));
    }
    
    • 在进入下个event loop阶段前,并且回调函数还没有释放回调权限时执行一些相关操作。如下代码:
    const EventEmitter = require('events');
    const util = require('util');
    
    function MyEmitter() {
      EventEmitter.call(this);
    
      // use nextTick to emit the event once a handler is assigned
      process.nextTick(function() {
        this.emit('event');
      }.bind(this));
    }
    util.inherits(MyEmitter, EventEmitter);
    
    const myEmitter = new MyEmitter();
    myEmitter.on('event', function() {
      console.log('an event occurred!');
    });
    

    在MyEmitter构造函数实例化前注册“event”事件,这样就可以保证实例化后的函数可以监听“event”事件。

    相关文章

      网友评论

        本文标题:Nodejs 解读event loop的事件处理机制

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