剖析NodeJs的事件轮询机制

来源:互联网 发布:ubuntu 开启ssh服务 编辑:程序博客网 时间:2024/05/01 20:47

前言:一直以来,对NodeJs的事件轮询机制一知半解。查阅了些许资料后,总算揭开了其神秘的面纱。


一、首先,看看Nodejs官网上对nodejs的描述:

Node takes the event model a bit further, it presents the event loop as a language construct instead of as a library. In other systems there is always a blocking call to start the event-loop. Typically one defines behavior through callbacks at the beginning of a script and at the end starts a server through a blocking call like EventMachine::run(). In Node there is no such start-the-event-loop call. Node simply enters the event loop after executing the input script. Node exits the event loop when there are no more callbacks to perform. This behavior is like browser JavaScript — the event loop is hidden from the user.

从加粗字体可以得出如下信息:

1)在每次执行脚本的时候,Node就开始进入事件轮询机制;

2)直到所有回调函数都被执行完成后,Node才会退出事件轮询机制;

3)Nodejs中的事件轮询机制的结构,对开发者是隐藏的,不可见的。


二、那么,所谓的“事件轮询”又是什么呢?

一个系统(或者说一个程序)中必须至少包含一个大的循环结构(暂且称之为“泵”,具体概念参考这篇文章http://www.cnblogs.com/xiaozhi_5638/p/4268613.html),它是维持系统持续运行的前提。在NodeJs中,一样也是包含这样一个循环结构,我们称之为“事件轮询”,它存在于主线程中,负责不停地调用开发者编写的代码。


三、你可能又有疑问了,Nodejs不是自我标榜“单线程、非阻塞”,为什么还会有主线程概念?

我们其实对 Node.js的单线程一直有个很深的误会。事实上,这里的“单线程”指的是我们(开发者)编写的代码只能运行在一个线程当中(习惯称之为主线程),Node.js并没有给 Javascript 执行时创建新线程的能力,所以称为单线程,也就是所谓的主线程。 其实,Nodejs中许多异步方法在具体的实现时(NodeJs底层封装了Libuv,它提供了线程池、事件池、异步I/O等模块功能,其完成了异步方法的具体实现),内部均采用了多线程机制。


四、那么,开发者编写的代码是怎样通过事件轮询来得到调用的呢?尤其是一些异步方法中带的回调函数?

如下图所示:

这里写图片描述

1)NodeJs程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以NodeJs始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。

2)在回调函数中,可能会进行耗时的异步I/0操作,这时,NodeJs底层便会调用Libuv完成异步函数的具体实现,因此,使用回调函数处理I/O操作,并不会阻塞主程序的运行。

3)每次异步函数执行结束后,都会在事件队列中追加一个事件(同时,保存一些必要的参数)。事件轮询下一次循环便可取出事件,然后会调用异步方法所绑定的回调函数(因为,回调函数是绑定在事件上的,监听的事件发生,其相对应的回调函数便会被触发执行)。这样一来,nodejs便能保证开发者编写的每行代码(每个回调函数)均在主线程中执行。

4)需要注意的一点是: 如果开发者在回调函数中调用了阻塞方法,那么整个事件轮询就会被阻塞,事件队列中的事件便得不到及时处理。


五、在回调函数中调用了阻塞方法,真的会阻塞事件轮询机制吗?

为了验证这个问题,我写了如下测试代码:

说明:
test01.txt 文本内容为 : 我是text01 !
test02.txt 文本内容为 : 我是text02 !

var fs = require('fs'),    path = require('path');fs.readFile(__dirname + '/test01.txt', {flag: 'r+', encoding: 'utf8'}, function (err, data) {    console.log(data); //打印test01.txt文本内容  //sleep(5000);     sleep(5000);  //延迟5s});fs.readFile(__dirname + '/test02.txt', {flag: 'r+', encoding: 'utf8'}, function (err, data) {    console.log(data); //打印test02.txt文本内容});//自己写的一个延迟函数function sleep(milliSeconds){    var StartTime =new Date().getTime();    while (new Date().getTime() <StartTime+milliSeconds);}

运行结果如下:

注释掉sleep(5000)代码后,控制台 几乎同时显示 我是test01 ! 和 我是test02 !

添加sleep(5000)代码后,控制台首先打印 我是test01 ! ,5s后再打印 我是test02 !

这个例子验证了这样一个情况:在回调函数中,使用阻塞方法,会阻塞整个事件轮询。

1 0
原创粉丝点击