深入了解nodejs的事件循环机制

栏目: Node.js · 发布时间: 7年前

内容简介:一直以来,我写的的大部分JS代码都是在浏览器环境下运行,因此也了解过浏览器的事件循环机制,知道有macrotask和microtask的区别。但最近写node时,发现node的事件循环机制和浏览器端有很大的不同,特此深入地学习了下。在传统web服务中,大多都是使用多线程机制来解决并发的问题,原因是I/O事件会阻塞线程,而阻塞就意味着要等待。而node的设计是采用了单线程的机制,但它为什么还能承载高并发的请求呢?因为node的单线程仅针对主线程来说,即每个node进程只有一个主线程来执行程序代码,但node

一直以来,我写的的大部分JS代码都是在浏览器环境下运行,因此也了解过浏览器的事件循环机制,知道有macrotask和microtask的区别。但最近写node时,发现node的事件循环机制和浏览器端有很大的不同,特此深入地学习了下。

单线程

在传统web服务中,大多都是使用多线程机制来解决并发的问题,原因是I/O事件会阻塞线程,而阻塞就意味着要等待。而node的设计是采用了单线程的机制,但它为什么还能承载高并发的请求呢?因为node的单线程仅针对主线程来说,即每个node进程只有一个主线程来执行程序代码,但node采用了事件驱动的机制,将耗时阻塞的I/O操作交给线程池中的某个线程去完成,主线程本身只负责不断地调度,并没有执行真正的I/O操作。也就是说node实现的是异步非阻塞式。

事件循环机制

node能实现高并发的诀窍就在于事件循环机制,这个事件循环机制和浏览器端的相似但也有很多不同。根据node的官方介绍,node每次事件循环机制都包含了6个阶段:

  • timers阶段:这个阶段执行已经到期的timer(setTimeout、setInterval)回调
  • I/O callbacks阶段:执行I/O(例如文件、网络)的回调
  • idle, prepare 阶段:node内部使用
  • poll阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check阶段:执行setImmediate回调
  • close callbacks阶段:执行close事件回调,比如TCP断开连接

对于日常开发来说,我们比较关注的是timers、I/O callbacks、check阶段。node和浏览器相比一个明显的不同就是node在每个阶段结束后会去执行所有microtask任务。对于这个特点我们可以做个试验:

console.log('main');

setImmediate(function() {
    console.log('setImmediate');
});

new Promise(function(resolve, reject) {
    resolve();
}).then(function() {
    console.log('promise.then');
});
复制代码

代码的执行结果是:

  1. main
  2. promise.then
  3. setImemediate

setImmediate 和 process.nextTick

相对于浏览器环境,node环境下多出了setImmediate和process.nextTick这两种异步操作。setImmediate的回调函数是被放在check阶段执行,即相当于事件循环的最后阶段了。而process.nextTick会被当做一种microtask,前面提到每个阶段结束后都会执行所有microtask任务,所以process.nextTick有种类似于插队的作用,可以赶在下个阶段前执行,但它和promise.then哪个先执行呢?通过一段代码来实验:

console.log('main');

process.nextTick(function() {
    console.log('nextTick')
})

new Promise(function(resolve, reject) {
    resolve();
}).then(function() {
    console.log('promise.then');
});
复制代码

代码的执行结果是:

  1. main
  2. nextTick
  3. promise.then

事实证明,process.nextTick的优先级会比promise.then高。

process.nextTick的饥饿陷阱

process.nextTick的优势在于它能够插入到每个阶段之后,在当前阶段执行完毕后就能立马执行。然而它的这个优点也导致了如果调用不当就容易陷入饥饿陷阱。具体就是当递归地调用process.nextTick的时候,事件循环一直无法进入到下一个阶段,导致了后面阶段的事件一直无法被执行,产生饥饿问题。

看一个例子就很容易明白

let i = 0;
setImmediate(function() {
    console.log('setImmediate');
});
function callback() {
    console.log('nextTick' + i++);
    if (i < 1000) {
        process.nextTick(callback);
    }
}
callback();
复制代码

执行的结果是 nextTick0 nextTick1 nextTick2 ... nextTick999 setImmediate

setImmediate的回调会一直等待到process.nextTick任务都完成后才能被执行。

小结

1.node的事件循环机制和浏览器的有所不同,多出了setImmediate 和 process.nextTick这两种异步方式。由于process.nextTick会导致I/O饥饿,所以官方也推荐使用setImmediate。 2.node虽然是单线程的设计,但它也能实现高并发。原因在于它的主线程事件循环机制和底层线程池的实现。 3.这种机制决定了node比较适合I/O密集型应用,而不适合CPU密集型应用。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Operating System Algorithms

Operating System Algorithms

Nathan Adams、Elisha Chirchir / CreateSpace Independent Publishing Platform / 2017-4-21 / USD 39.15

Operating System Algorithms will walk you through in depth examples of algorithms that you would find in an operating system. Selected algorithms include process and disk scheduling.一起来看看 《Operating System Algorithms》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具