nodejs中的eventLoop

484 阅读3分钟

javascript与nodejs的事件环

nodejs的event是基于libuv,而浏览器的event loop则在html5的规范中明确定义。

浏览器中的事件环

js中的事件环

  1. 所有同步任务都在主线程上执行,形成一个执行栈
  2. 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将队列中的事件放到执行栈中依次执行
  4. 主线程从任务队列中读取事件,这个过程是循环不断的
console.log(1)
setTimeout(function(){
    console.log(2)
    setTimeout(function(){
        console.log(3)
    })
})
setTimeout(function(){
    console.log(4)
})
console.log(5)
  1. 打印结果:15243
  2. 存放在任务队列中的事件有:onclick,onload,ajax,setTimeout,setInterval等异步方法
  3. 执行栈中的方法总是在任务队列前执行,任务队列中的执行顺序是先进先出

nodejs中的事件环

Node.js也是单线程,但是它的event loop运行机制不同于浏览器环境。

nodejs中的事件环

  1. 我们写的js代码会交给v8引擎进行处理
  2. 代码中可能会调用nodeApi,node会交给libuv库处理
  3. libuv通过阻塞i/o和多线程实现了异步io
  4. 通过事件驱动的方式,将结果放到事件队列中,最终交给我们的应用。

nodejs中事件循环的几个阶段 在libuv内部有这样一个事件环机制。在node启动时会初始化事件环

   ┌───────────────────────┐
┌─ │     timers(计时器)     │
|  |   执行setTimeout以及   |
|  |   setInterval的回调。  |
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     |
│  |   处理网络、流、tcp的错误 |
|  | callback              |
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
|  |     node内部使用       |
│  └──────────┬────────────┘      
│  ┌──────────┴────────────┐      ┌───────────────┐ 
│  │       poll(轮询)       │      │   incoming:   │
|  | 执行poll中的i/o队列     | ─────┤  connections, │
|  | 检查定时器是否到时       |      │   data, etc.  |  
│  └──────────┬────────────┘      └───────────────┘    
│  ┌──────────┴────────────┐      
│  │      check(检查)       │
|  | 存放setImmediate回调    |
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    |
   │ 关闭的回调例如          |
   | sockect.on('close')   |
   └───────────────────────┘

阶段总览

  1. timers:执行setTimeout()和setInterval安排的回调
  2. I/O callbacks: 执行几乎所有异常的close回调,由timer和setImmediate执行的回调。
  3. idle,prepare: 只用于内部
  4. poll : 获取新的I/O事件,node在该阶段会适当的阻塞
  5. check : setImmediate的回调被调用
  6. close callbacks: e.g socket.on(‘close’,…);
  7. 在每次运行事件循环之间,node.j检查是否有正在等待的异步i/o调用、timers等。如果没有,就清除并结束(退出程序),例如:执行一个程序,仅有一句话(var a= ‘hello’;),处理完目标代码后,不会进入evetloop,而是直接结束程序。

阶段详解

  1. timers,定时器阶段: 执行定时任务(setTimeOut(), setInterval())
  1. setTimeout 和 setImmediate 二者非常相似,但是二者区别取决于他们什么时候被调用.
  • setImmediate 设计在poll阶段完成时执行,即check阶段;
  • setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行;但其在timer阶段执行 其二者的调用顺序取决于当前event loop的上下文,如果他们在异步i/o callback之外调用(在i/o内调用因为下一阶段为check阶段),其执行先后顺序是不确定的,需要看loop的执行前的耗时情况
  1. poll阶段

poll 轮询阶段:

  1. 处理到期的定时器任务,然后(因为最开始阶段队列为空,一旦队列为空,就会检查是否有到期的定时器任务)
  2. 处理队列任务,直到队列空,或达到上限
  3. 如果队列为空:如果setImmediate,终止轮询阶段,进入检查阶段执行。如果没setImmediate,查看有没有定时器任务到期,有的话就到timers阶段,执行回调函数.
  1. check阶段

poll阶段变为空闲、等待状态时,一旦调用setImmediate(),eventloop会进入check 阶段,而不是在poll阶段等待。

  1. close callbacks阶段

例如:socket或句柄关闭,close事件会触发这个阶段。或者通过process.nextTick()触发

参考文件

nodejs官方文档之event