新手入门:理解Nodejs的异步非阻塞I/O模型
新手入门:理解Nodejs的异步非阻塞I/O模型
今天.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,也就事半功倍了!
新手入门:理解Nodejs的异步非阻塞I/O模型
在今天的课堂上,老师提到了.NET平台中的多线程编程,让我想起了Node.js单线程异步非阻塞I/O模型的魅力。Node.js通过其独特的单线程模型和事件驱动架构,展现了单线程编程的强大能力。
阻塞I/O
在传统编程模式中,当程序执行到需要进行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
语句会被执行,但由于阻塞I/O的存在,这些语句会在I/O操作完成之前无法继续执行。假设读取文件需要10秒,那么在这10秒里,程序实际上处于空闲状态。
非阻塞I/O
非阻塞I/O允许在I/O操作期间继续执行其他代码,从而提高了程序的效率。Node.js正是通过这种方式实现了高效的异步编程。
下面是一个使用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
命令运行它。你会看到以下输出:
我不会被阻塞!
我读完文件了!
这里的关键在于 fs.readFile
方法。该方法接受一个回调函数作为参数,当文件读取完成后,这个回调函数会被调用。在这期间,程序不会停止执行,而是继续运行后续代码。
Node.js事件循环机制(event loop)
Node.js的核心是其事件循环机制。事件循环不断地检查是否有待处理的任务或事件。当一个任务完成时,相应的回调函数会被推入事件队列中,等待执行。事件循环会依次执行这些回调函数,确保所有I/O操作都是非阻塞的。
Mixu在他的博文中提到的一句话非常经典:“Everything runs in parallel except your code!” 这句话的意思是,在Node.js中,除了你的代码外,其他一切都可以并行执行。理解这一点,对于学习Node.js至关重要。
总结一下,Node.js的异步非阻塞I/O模型通过事件循环机制实现了高效的I/O处理,使得开发者可以编写出高性能的单线程应用。
.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…
有什么好唏嘘的,这做法怎么麻烦了。
你好我想请问下,如果代码如下
var fs = require("fs");
fs.writeFile("./testfile", "utf8", function(error, file) {
if (error) throw error;
console.log("我保存文件了!");
});
console.log("我不会被阻塞!");
输出顺序是不会改变,但是我想请问下,是文件先被写入完成,还先输出 “我不会被阻塞!”(即 顺序是否为: wirteFile保存文件 -> “我不会被阻塞!” -> function回调函数 -> “我保存文件了!” ???)
不要这么锱铢必较嘛:)
Node 的 异步非阻塞I/O,是建立在 C/C++ 多线程基础上的。。。
搞不清楚 异步和 非阻塞 这两个概念不同吧
在Node.js中,使用异步非阻塞I/O模型可以显著提升程序的性能。与传统阻塞I/O模型相比,Node.js不会让线程等待I/O操作完成,而是通过事件循环机制来处理I/O操作。这样,当I/O操作正在进行时,Node.js可以继续执行其他任务。
下面是一个简单的示例,展示了如何在Node.js中实现异步非阻塞I/O:
var fs = require("fs");
// 异步读取文件
fs.readFile("./testfile", "utf8", function(error, file) {
if (error) throw error;
console.log("我读完文件了!");
});
// 这句代码会立即执行
console.log("我不会被阻塞!");
在这个例子中,fs.readFile()
方法用于读取文件,但并不会阻塞后续代码的执行。当文件读取完成后,回调函数会被调用,打印出 “我读完文件了!”。而在这之前,“我不会被阻塞!” 这条消息已经提前打印出来。
这种方式使得Node.js非常适合处理高并发场景,因为它允许程序在等待I/O操作的同时继续处理其他任务,从而充分利用了CPU资源。