nodejs事件轮询详述
目录
概述
关于nodejs的介绍网上资料非常多,最近由于在整理一些函数式编程的资料时,多次遇到nodejs有关的内容。所以就打算专门写一篇文章总结一下nodejs相关知识,包括“说它单线程是什么意思”、“非阻塞又是指什么”以及最重要的是它的“事件轮询”的实现机制。
本文不介绍nodejs的优缺点(适用场合)、nodejs环境怎样搭建以及一些nodejs库的使用等等这些基础知识。
nodejs特点
网上任何一篇关于nodejs的介绍中均会提及到nodejs两个主要特点:单线程、非阻塞。但是据我所了解到的,大部分介绍一带而过,并没有详细地、系统性地去说明它们到底是怎么回事。下面我依次尽我所能详细地说一下我对以上两者的理解。
非阻塞
我们先来看一段.NET中异步编程的代码:
using(FileStream fs = new FileStream("hello.txt", FileMode.Open))
{
byte[] data = new byte[fs.Length];
fs.BeginRead(data, 0, fs.Length, new AsyncCallback(onRead), null);
Console.WriteLine("the end");
}
如上代码所示,由于FileStream.BeginRead是一个异步方法,所以不管hello.txt文件有多大,FileStream.BeginRead方法的调用并不会阻塞调用线程,Console.WriteLine方法立马便可执行。同理,如果在nodejs中所有的方法都是“异步方法”,那么在nodejs中任何方法的调用均不会阻塞调用线程,实质上,nodejs中大部分库方法确实是这样的。这就是为什么我们会说nodejs中代码是非阻塞的。
单线程
对这个概念有误解的人非常之多,以为nodejs程序中就一个线程,然后有很多人会问:既然只有一个线程,那么怎么并行处理多个任务呢?
其实这里说的单线程并不是指nodejs程序中只有一个线程存在,我个人感觉官方给出“单线程”说法本身就具有误导性,所以也怪不得大部分初学者。那么“单线程”到底什么意思呢?其实这里的“单线程”指的是我们(开发者)编写的代码只能运行在一个线程当中(可以称之为主线程吧),就像我们在Windows桌面程序开发中一样,编写的所有界面代码均运行在UI线程之中。
那么还是刚才那个问题,所有编写的代码均运行在一个线程中,那么怎样去并行处理任务呢?这个就要想到前面介绍的“异步方法”了,没错,虽然开发者编写的所有代码均运行在一个线程中,但是我们可以在这个线程中调用异步方法啊,而异步方法内部实现过程当然要采用多线程了。就像下图:

如上图所示,nodejs中的单线程指的是图中的主线程,该主线程中包含一个循环结构,维持整个程序持续运转。
注:该循环结构也称之为“泵”结构,是每个系统必备的结构。具体可以参见我之前的一篇博客《动力之源:代码中的泵》。
因此我们可以说,在nodejs中写的代码(包括回调方法)均只运行在一个线程中,但是不代表它只有一个线程。nodejs中许多异步方法在具体的实现时,内部均采用了多线程机制(具体后面会讲到)。
事件轮询
如果看过我前面博客的一些读者可能知道,一个系统(或者说一个程序)中必须至少包含一个大的循环结构(我称之为“泵”),它是维持系统持续运行的前提。nodejs中一样包含这样的结构,我们叫它“事件轮询”,它存在于主线程中,负责不停地调用开发者编写的代码。我们可以查看nodejs官方网站上对nodejs的说明:

我们可以看到,在nodejs中这个“循环”结构对开发者来讲是不可见的。
那么开发者编写的代码是怎样通过事件轮询来得到调用的呢?尤其是一些异步方法中带的回调函数?看下面一张图:

如上图所示,每个异步函数执行结束后,都会在事件队列中追加一个事件(同时保存一些必要参数)。事件轮询下一次循环便可取出事件,然后会调用异步方法对应的回调函数(参数)。这样一来,nodejs便能保证开发者编写的每行代码(每个回调)均在主线程中执行。注意这里有一个问题,如果开发者在回调函数中调用了阻塞方法,那么整个事件轮询就会阻塞,事件队列中的事件得不到及时处理。正因为这样,nodejs中的一些库方法均是异步的,也提倡用户调用异步方法。
其实看到这里的时候,如果有对Windows编程(尤其对Windows界面编程)比较了解的读者可能已经联想到了Windows消息循环。

没错,nodejs中的事件轮询原理跟Windows消息循环的原理类似。开发者编写的代码均运行在主线程中,如果你编写了阻塞代码,在Windows桌面程序中,由于消息得不到及时处理,界面就会卡死。
咱们再来看一下下面的nodejs代码:
var fs = require('fs');
fs.readFile('hello.txt', function (err, data) { //异步读取文件
console.log("read file end");
});
while(1)
{
console.log("call readFile over");
}
如上,虽然我们使用异步方法读取文件,但是文件读取完毕后“read file end”永远不会输出,也就是说readFile方法的回调函数不会执行。原因很简单,因为后面的while循环一直没退出,导致下一次事件轮询不能开始,所以回调函数不能执行(包括其他所有回调)。事实再次证明,开发者编写的所有代码均只能运行在同一线程之中(姑且称之为主线程吧)。
关于异步方法
所谓异步方法,就是调用该方法不会阻塞调用线程,哪怕方法内部要进行耗时操作。你可以理解为方法内部单独开辟了一个新线程去处理任务,而调用异步方法仅仅是开启这个新线程。下面的代码模拟一个异步方法的内部结构(仅仅是模拟,不代表实际):
public void DoSomething(int arg1,AsyncCallback callback)
{
(Action)(delegate()
{
Thread.Sleep(1000*20); //模拟耗时操作
if(callback != null)
{
callback(...); //调用回调函数
}
}).BeginInvoke(null,null); }
如上代码所示,调用DoSomething方法不会阻塞调用线程。那么对于每一个异步方法,怎样去判断异步操作是否执行完毕呢?这时候必须给异步方法传递一个回调函数作为参数,在.NET中,这个回调参数一般是AsyncCallback类型的。如大家所熟知的FileStream.BeginRead/BeginWrite以及Socket.BeginReceive/BeginSend等等均属于该类方法。
但是,我之所以要提异步方法,就是想让大家区分nodejs中的异步方法和.NET中异步方法的一个重大区别,虽然两者内部原理可以理解为一致的,但是在回调函数的调用方式这一点上,两者有截然不同的方式。
在.NET中,每个异步方法的回调函数均在另外一个线程中执行(非调用线程),而在nodejs中,每个异步方法的回调函数仍然还在调用线程上执行。至于为什么,大家可以看一下前面讲事件轮询的部分,nodejs中每个回调函数均由主线程中的事件轮询来调用。这样才能保证在nodejs中,开发者编写的任何代码均在同一个线程中运行(所谓的单线程)。
注:不懂调用线程、当前线程是什么意思的同学可以看一下这篇博客:《高屋建瓴:梳理编程约定》。
nodejs事件轮询详述的更多相关文章
- 12.nodejs事件轮询机制
一:nodejs事件轮询机制 就是 函数的执行顺序 <script type="text/javascript"> setImmediate(function(){ ...
- nodejs,事件轮询总结
宏任务 script,setTimeoout,setInterval,setlmmediate(node 独有),I/o,render渲染 微任务 process.nextTick(),promise ...
- 理解Node.js的事件轮询
前言 总括 : 原文地址:理解Node.js的事件轮询 Node小应用:Node-sample 智者阅读群书,亦阅历人生 正文 Node.js的两个基本概念 Node.js的第一个基本概念就是I/O操 ...
- 面试题: nodejs 的事件轮询机制
setTimeout(function(){ console.log('setTimeout()执行了') },0) setImmediate(function(){ console.log('set ...
- node.js事件轮询(1)
事件轮询(引用) 事件轮询是node的核心内容.一个系统(或者说一个程序)中必须至少包含一个大的循环结构(我称之为"泵"),它是维持系统持续运行的前提.nodejs中一样包含这样的 ...
- 对Node.JS的事件轮询(Event Loop)的理解
title: Node.JS的事件轮询(event loop)的理解 categories: 理解 tags: Node JS 机制 当我们知道I/O操作和创建新线程的开销是巨大的! 网站延迟的开销 ...
- Node.js的事件轮询Event Loop原理
Node.js的事件轮询Event Loop原理解释 事件轮询主要是针对事件队列进行轮询,事件生产者将事件排队放入队列中,队列另外一端有一个线程称为事件消费者会不断查询队列中是否有事件,如果有事件,就 ...
- 理解JavaScript中的事件轮询
原文:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 为什么JavaScript是单线程 JavaScript语言的一大特点就是单线程,也 ...
- JS中的异步以及事件轮询机制
一.JS为何是单线程的? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊.(在JAVA和c#中的异步 ...
随机推荐
- Android应用请求获取Root权限
应用获取Root权限的原理:让应用的代码执行目录获取最高权限.在Linux中通过chmod 777 [代码执行目录] /** * 应用程序运行命令获取 Root权限,设备必须已破解(获得ROOT权限) ...
- 10gRAC vip启动报错CRS-1006 CRS-0215
为测试一个迁移方案,装了一套10g rac环境,可能是很久没有装过10g的RAC了,整个过程情况不断. 1.在把集群软件和数据库软件都装好之后,用crs_stat检测状态的时候,发现vip的状态不对, ...
- CVE-2015-7645 analyze and exploit
Hack team之后adobe和google合作对flash进行了大改,一度提高了flash的利用门槛,CVE-2015-7645作为第一个突破这些限制的漏洞利用方式,可以作为vetect利用方式之 ...
- 获取html 中的内容 将前台的数据获取到后台
使用js创建一个form表单 ,使用post上传到后台中 下面是代码.在获取html内容的时候使用了js节点来获取内容. parent:父节点.上一级的节点 siblings:兄弟节点.同一级别的节点 ...
- Daily Scrum Meeting ——FourthDay(Beta)12.12
一.Daily Scrum Meeting照片 讨论界面优化详情 二.Burndown Chart 项目有条不紊地进行中... 1.新增6个界面修改计划 2.修复两个BUG 三.项目进展(check- ...
- iframe中positioin:fixed失效问题
页面中嵌套的iframe 内的 position:fixed元素定位失效fixed正常页面 此时position:fixed是根据浏览器窗口定位的,下拉一直位于左上角:以iframe形式嵌入后 此时p ...
- 【转】oracle中rowid的用法 (全面)
ROWID是数据的详细地址,通过rowid,oracle可以快速的定位某行具体的数据的位置. ROWID可以分为物理rowid和逻辑rowid两种.普通的堆表中的rowid是物理rowid,索引组织表 ...
- unison+inotify-tools触发式双向自动同步
双向实时数据同步部署 首先添加服务器ssh信任,即免秘钥登陆 Web1:192.168.10.36 Web2:192.168.10.37 分别在web1和web2上执行以下命令 mkdir ~/.ss ...
- tornado 学习笔记16 HTTP1Connection
HTTP/1.x协议的具体实现.实现HTTPConnection接口. 16.1 构造函数 定义: def __init__(self, stream, is_client, params=None, ...
- 【异常】Caused by: java.lang.ClassNotFoundException: org.springframework.dao.DataIntegrityViolationException
Caused by: java.lang.ClassNotFoundException: org.springframework.dao.DataIntegrityViolationException ...