目录

概述

关于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. Range Sum Query - Mutable

    https://leetcode.com/problems/range-sum-query-mutable/ 因为数组会变动,所以缓存机制受到了挑战...每次更新数组意味着缓存失效,这样一更新一查找的 ...

  2. 为什么说在使用多条件判断时switch case语句比if语句效率高?

    在学习JavaScript中的if控制语句和switch控制语句的时候,提到了使用多条件判断时switch case语句比if语句效率高,但是身为小白的我并没有在代码中看出有什么不同.去度娘找了半个小 ...

  3. MySQL开发规范

    字段设计 (1)建议使用UNSIGNED存储非负数值. (2)建议使用INT UNSIGNED存储IPV4. (4)INT类型固定占用4字节存储,例如INT(4)仅代表显示字符宽度为4位,不代表存储长 ...

  4. Andriod学习笔记4:mac下搭建 Eclipse+CDT 集成开发环境

    下载CDT 从eclipse官网下载最新的Eclipse IDE for C/C++ Developers,例如eclipse-cpp-mars-1-macosx-cocoa-x86_64.tar.g ...

  5. 高效快捷实用移动开单手持扫描打印一体智能 POS PDA

    PDA数据采集器,是一款移动手持开单设备,它通过WIFI和GPRS连接并访问电脑,从进销存软件中读取数据,实现移动开单,打破电脑开单模式. 它自带扫描器,可直接扫描条码来查找产品,且功能强大.操作简单 ...

  6. C练习

    int main() { int a; //scanf函数只接受变量的地址 //scanf函数是一个阻塞式的函数,等待用户输入 // 用户输入完成后,就会将用户输入的值赋值给变量a //函数调用完毕 ...

  7. select2插件不兼容ie7,ie7下样子显示错位问题

    1.源文件(未修改) select2.min.css.select2.min.js 2.ie7下显示样式: 3.ie8下显示样式: 4.经查看发现ie7下对一些属性的解析和ie8不同,需对ie7另作h ...

  8. Easyui之datagrid实现点击单元格修改单元格背景颜色

    前段时间有个需求中有点击datagrid的单元格实现某种事件,调用datagrid的onclickCell这个方法很容易实现,但是体验不好啊,完全不知道自己刚才点击的是哪个单元格,然后就尝试单击单元格 ...

  9. 一些牛逼哄哄的javascript面试题

    今天我们来对这5个题目详细分析一下,希望对大家有所帮助. 注: 问题来自大名鼎鼎的前端架构师Baranovskiy的帖子<So, you think you know JavaScript?&g ...

  10. Linux Shell 编程中的特殊符号

    一.井号 # 1.在脚本文件中对一行进行注释. 2.在引号和\符号后不是注释,只是#号本身: echo "12 # hehe" echo '12 # hehe' echo 12 \ ...