目录

概述

关于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事件轮询详述的更多相关文章

  1. 12.nodejs事件轮询机制

    一:nodejs事件轮询机制  就是  函数的执行顺序 <script type="text/javascript"> setImmediate(function(){ ...

  2. nodejs,事件轮询总结

    宏任务 script,setTimeoout,setInterval,setlmmediate(node 独有),I/o,render渲染 微任务 process.nextTick(),promise ...

  3. 理解Node.js的事件轮询

    前言 总括 : 原文地址:理解Node.js的事件轮询 Node小应用:Node-sample 智者阅读群书,亦阅历人生 正文 Node.js的两个基本概念 Node.js的第一个基本概念就是I/O操 ...

  4. 面试题: nodejs 的事件轮询机制

    setTimeout(function(){ console.log('setTimeout()执行了') },0) setImmediate(function(){ console.log('set ...

  5. node.js事件轮询(1)

    事件轮询(引用) 事件轮询是node的核心内容.一个系统(或者说一个程序)中必须至少包含一个大的循环结构(我称之为"泵"),它是维持系统持续运行的前提.nodejs中一样包含这样的 ...

  6. 对Node.JS的事件轮询(Event Loop)的理解

    title: Node.JS的事件轮询(event loop)的理解 categories: 理解 tags: Node JS 机制 当我们知道I/O操作和创建新线程的开销是巨大的! 网站延迟的开销 ...

  7. Node.js的事件轮询Event Loop原理

    Node.js的事件轮询Event Loop原理解释 事件轮询主要是针对事件队列进行轮询,事件生产者将事件排队放入队列中,队列另外一端有一个线程称为事件消费者会不断查询队列中是否有事件,如果有事件,就 ...

  8. 理解JavaScript中的事件轮询

    原文:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 为什么JavaScript是单线程 JavaScript语言的一大特点就是单线程,也 ...

  9. JS中的异步以及事件轮询机制

    一.JS为何是单线程的? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊.(在JAVA和c#中的异步 ...

随机推荐

  1. HDU5887 Herbs Gathering(2016青岛网络赛 搜索 剪枝)

    背包问题,由于数据大不容易dp,改为剪枝,先按性价比排序,若剩下的背包空间都以最高性价比选时不会比已找到的最优解更好时则剪枝,即 if(val + (LD)pk[d].val / (LD)pk[d]. ...

  2. C#运算符号

    double x=5.1e3;// 5.1乘以10 的3次方. x就是 5100 //注 :  5.1e+3=5.1e3=5.1e03=5.1e+03 double y=5.1e-3;// 5.1乘以 ...

  3. UWP学习记录10-设计和UI之控件和模式7

    UWP学习记录10-设计和UI之控件和模式7 1.导航控件 Hub,中心控件,利用它你可以将应用内容整理到不同但又相关的区域或类别中. 中心的各个区域可按首选顺序遍历,并且可用作更具体体验的起始点. ...

  4. CTSC是啥

    洛谷看到一题的难度NOI/NOI+/CTSC 百度一下 CTSC (China Team Selection Competition)为国际信息学奥林匹克竞赛(International Olympi ...

  5. OpneCv2.x 模块结构

    转自:http://blog.csdn.net/huang9012/article/details/21811271 之前啃了不少OpenCV的官方文档,发现如果了解了一些OpenCV整体的模块架构后 ...

  6. CURL 模拟http提交

    1:CURL模拟get提交 private function httpGet($url) { $curl = curl_init(); curl_setopt($curl, CURLOPT_RETUR ...

  7. js三种方法添加image

    1 var img = new Image(); 2 var img = document.createElement('image'); 3 img.innerHtml = '<img src ...

  8. Git 创建本地仓库

    前面已经搭好环境了,现在我们缺的是一个管理版本控制的仓库.这次的实验是在电脑本地创建本地仓库.指定路径 默认的位置是在你所安装Git的目录下.Git的仓库你可以建在你电脑的任何目录下(最好不要包含有中 ...

  9. [转]svn 回退/更新/取消至某个版本命令详解

    1. 取消Add/Delete 取消文件 svn revert 文件名 取消目录 svn revert --depth=infinity 目录名 2. 回退版本 方法1: 用svn merge 1) ...

  10. C++-文件【1】-按行读文本文件的两种方法

    测试环境—— 系统:Win7 64bit 编译器:TDM-GCC 4.9.2 64-bit Release #include <iostream> #include <fstream ...