nodejs事件轮询详述

转载 2015年11月19日 20:24:57

转载自:http://www.cnblogs.com/xiaozhi_5638/p/4816265.html?utm_source=tuicool&utm_medium=referral

目录

 

概述

关于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中,开发者编写的任何代码均在同一个线程中运行(所谓的单线程)。

注:不懂调用线程、当前线程是什么意思的同学可以看一下这篇博客:《高屋建瓴:梳理编程约定》


相关文章推荐

init.c轮询注册事件

  • 2011年11月14日 00:41
  • 70KB
  • 下载

原生javascript+nodejs 配合worker实现高效快速的即时聊天室功能非websocket,无轮询请求

首先列出文件目录 index.js //nodejs启动文件 mime.js //MIME类型表 Routing.js //路由表 RL文件夹 RL/Coupons.js //路...

lua 协程方案实现消息机制 | 事件队列轮询处理机制

Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。 游戏中,有时在处理消息时,希望一条一条消息独立...

Java异步事件:轮询与中断

CPU几乎把所有的时间都花费在从内存获取指令并运行它们的过程中。然而,CPU和主存仅仅只是计算机硬件系统中众多组件的其中两个。一个完整的系统还包含其他的设备,比如:硬盘或者固态硬盘,用来存储程序和数据...

【译】理解node.js的事件轮询

在ManUel Kiessling的《The Node Beginner Book》 Node入门书(http://www.nodebeginner.org/index-zh-cn.html)中说明了...

Redis2.2.2源码学习——aeEvent事件轮询

Version Redis2.2.2 Redis的事件主要分为文件事件和定时器时间,作者对这两种事件处理的高端之处在于预先计算最近一个要超时的定时器距离当前的事件间隔,在这个时间间隔内调用poll函...
  • ordeder
  • ordeder
  • 2013年10月16日 21:09
  • 3829

select、poll、epoll的比较-select轮询+sleep,epoll异步事件驱动高效

select、poll、epoll的比较 http://my.chinaunix.net/space.php?uid=20196318&do=blog&id=366042 http://b...
  • klarclm
  • klarclm
  • 2013年01月29日 10:27
  • 2812

理解Mixu对node.js事件轮询的说法

在ManUel Kiessling的《The Node Beginner Book》 Node入门书(http://www.nodebeginner.org/index-zh-cn.html)中说明了...

时间片轮询与状态机

  • 2015年09月22日 16:45
  • 652KB
  • 下载

自动区分ua轮询跳转源码

  • 2010年01月09日 11:16
  • 491B
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:nodejs事件轮询详述
举报原因:
原因补充:

(最多只允许输入30个字)