精华 新手入门:理解Node.js的异步非阻塞I/O模型
发布于 7 年前 作者 zhiguang 45333 次浏览 最后一次编辑是 3 年前

今天.NET老师在课堂上吹捧多线程编程,我就想为单线程抱个不平,因为Node的单线程异步非阻塞I/O模型,演绎了单线程编程的神话。

阻塞I/O 程序执行过程中必然要进行很多I/O操作,读写文件、输入输出、请求响应等等。I/O操作时最费时的,至少相对于代码来说,在传统的编程模式中,举个例子,你要读一个文件,整个线程都暂停下来,等待文件读完后继续执行。换言之,I/O操作阻塞了代码的执行,极大地降低了程序的效率。

下面是是一个C#读文件的例子:

private string ReadTxtToStr(string filename)
{   
    //打开文件,打开期间其他代码停止执行,直到完成打开后继续执行代码。   
    FileStream fs = File.Open(filename, FileMode.Open);    
    Console.WriteLine("我被打开文件阻塞了。");    
    StreamReader sr = new StreamReader(fs);    
    //读取文件,读取期间其他代码停止执行,直到完成读取后继续执行代码。    
    string str=sr.ReadToEnd();    
    Console.WriteLine("我被读取文件阻塞了。");    
    return str;
}

在上述代码中,两个Console.WriteLine()虽然会被执行,但是却被无辜地阻塞一段时间。理论上,如果读取这个文件需要10秒,我们就浪费了10秒在I/O等待中(实际程序运行中有很大一部分时间是浪费在I/O等待上的),在码农眼里这可是天文数字。 Having asynchronous I/O is good, because I/O is more expensive than most code and we should be doing something better than just waiting for I/O. 非阻塞I/O 理解了阻塞I/O,非阻塞I/O就好理解。非阻塞I/O是程序执行过程中,I/O操作不会阻塞程序的执行,也就是在I/O操作的同时,继续执行其他代码(这得益于Node的事件循环机制)。在I/O设备效率还远远低于CPU效率的时代,这种I/O模型(非阻塞I/O)为程序带来的性能上的提高是非常可观的。

好,下面感受一下怎么用Node.js实现非阻塞I/O,继续读文件,看码:

var fs = require("fs");
fs.readFile("./testfile", "utf8", function(error, file) {  
     if (error) throw error;  
     console.log("我读完文件了!");
});
console.log("我不会被阻塞!");

复制上面代码保存为test.js,并在同一目录下新建一个名为testfile的文件,用node命令运行test.js,你将看到以下输出:

我不会被阻塞!

我读完文件了!

这显然不符合传统的程序执行顺序,注意,这就是Node.js的非阻塞I/O了。

首先解释下面程序,如果你熟悉JavaScript,请忽略。

var fs = require("fs");

以上代码:引入Node.js内置的File System文件系统模块fs。require()相当与Java的import,C++的include。

fs.readFile("./testfile", "utf8", function(error, file) { 
    if (error) throw error; 
    console.log("我读完文件了!");
});

以上代码:进行I/O操作,给readFile绑定一个回调函数function(error,file){},并在读取testfile完成后执行回调函数。期间,后面的代码继续执行,不受I/O阻塞。

这就是为什么先看到“我不会被阻塞!”而后看到“我读完文件了!”的缘故。

Node.js事件轮询机制(event loop) 《Node入门》推荐我们去读一下Mixu的一篇关于事件轮询的博文,的确值得一读,我英语一般,开着词典还能勉强看,略懂吧。

Mixu说的最经典的一句话:

Everything runs in parallel except your code!

(在Node中)除了代码,一切都是并行的!

理解这句话,再去学Node,也就事半功倍了!

16 回复

.NET里也可以异步读取Stream滴,只是麻烦所以很少人用。

NodeJS里因为只有麻烦的做法,大家叫好。

嘘唏不已。

Mixu说的最经典的一句话:

Everything runs in parallel except your code!

(在Node中)除了代码,一切都是并行的! ==》对于单CPU系统来说,不能算并行,只能说是并发而已

好文章,适合像我这样的新手!之前研究java时,总被java中的多线程环境,并发运行所震撼,现在发现单线程、异步非阻赛的I/O模型更加精妙!

只是还没弄懂的是在单线程环境下,nodeJS中非阻塞的I/O是怎么实现的!!!望楼住赐教……

一楼说的没错,很多语言都能实现异步I/O,主要是通过一个回调机制,可以看看http://www.ibm.com/developerworks/cn/linux/l-callback/ 这篇文章,

异步非阻塞底层的实现还是多线程,so…

有什么好唏嘘的,这做法怎么麻烦了。

@azumiwang 不麻烦?你给我写个冒泡排序的动画出来看看,比如这种。

冒泡排序算法都可以给你:

var compare = function (x, y) {
    return x - y;
};

var swap = function (array, i, j) {
    var t = array[i];
    array[i] = array[j];
    array[j] = t;
};
    
var bubbleSort = function (array) {
    for (var i = 0; i < array.length; i++) {
        for (var j = 0; j < array.length - i; j++) {
            var r = compare(array[j], array[j + 1]));
            if (r > 0) swap(array, j, j + 1);
        }
    }
};

你只要把setTimeout加到swap和compare方法里去就行了,只要加这么一个异步方法。

你好我想请问下,如果代码如下

    var fs = require("fs");
    fs.writeFile("./testfile", "utf8", function(error, file) {  
        if (error) throw error;  
        console.log("我保存文件了!");
    });
    console.log("我不会被阻塞!");

输出顺序是不会改变,但是我想请问下,是文件先被写入完成,还先输出 “我不会被阻塞!”(即 顺序是否为: wirteFile保存文件 -> “我不会被阻塞!” -> function回调函数 -> “我保存文件了!” ???)

@jeffz

(function () { "use strict";

var IntIterator = function(min,max) { this.min = min; this.max = max; }; IntIterator.prototype = { next: function() { return this.min++; } ,hasNext: function() { return this.min < this.max; } } var Main = function() { } Main.main = function() { Test.main(); } var async = {} async.Build = function() { } var Test = function() { } Test.interfaces = [async.Build]; Test.bubblesort = function(array,__cb) { var swapping = false; var temp; var __afterLoop4 = function() { __cb(null,array); }; var __loop3 = (function($this) { var $r; var __loop31 = null; __loop31 = function() { if(!swapping) { swapping = true; var __afterLoop1 = function() { __loop31(); }; var __iter2 = new IntIterator(0,array.length); var __loop0 = (function($this) { var $r; var __loop01 = null; __loop01 = function() { if(__iter2.hasNext()) { var i = __iter2.next(); Test.delay(10,function(__e) { if(__e == null) { if(array[i] > array[i + 1]) { temp = array[i + 1]; array[i + 1] = array[i]; array[i] = temp; swapping = false; } __loop01(); } else __cb(__e,null); }); } else __afterLoop1(); }; $r = __loop01; return $r; }(this)); __loop0(); } else __afterLoop4(); }; $r = __loop31; return $r; }(this)); __loop3(); } Test.asynchronous = function($int,string,__cb) { var arry; Test.bubblesort([1337,1,-465,3.141592653589793,789,69,789,-132,3.141592653589793,465,789,0,27],function(__e,__0) { if(__e == null) { arry = __0; console.log(arry); __cb(null); } else __cb(__e); }); } Test.main = function() { console.log(“start”); Test.asynchronous(10,“string”,function(err) { if(err != null) console.log(“Error: " + err); }); } Test.delay = function(ms,cb) { haxe.Timer.delay(function() { console.log(ms + " passed”); cb(null); },ms); } var haxe = {} haxe.Timer = function(time_ms) { var me = this; this.id = setInterval(function() { me.run(); },time_ms); }; haxe.Timer.delay = function(f,time_ms) { var t = new haxe.Timer(time_ms); t.run = function() { t.stop(); f(); }; return t; } haxe.Timer.prototype = { run: function() { console.log(“run”); } ,stop: function() { if(this.id == null) return; clearInterval(this.id); this.id = null; } } Test.meta = { statics : { bubblesort : { async : null}, asynchronous : { async : null}}}; Main.main(); })();

不要这么锱铢必较嘛:)

Node 的 异步非阻塞I/O,是建立在 C/C++ 多线程基础上的。。。

搞不清楚 异步和 非阻塞 这两个概念不同吧

回到顶部