本篇文章将介绍在Node.js中Blocking 和Non-Blocking调用的不同点。文中特指event loop 和libuv. 并不去阐述Blocking和Non-Blocking的基础知识。读者需要对javascript 语言和Node.js的回调模式有个基础的了解。
“IO” 指的是通过libv实现与系统硬盘和网络的交互。
libv是个专为Nodejs编写的一个跨平台的基础库。它围绕事件驱动异步IO模型来设计的。下图libv库展示了各个子系统的构成。
libv中的I/O loop的流程图如下
对该流程图的详细解释见http://docs.libuv.org/en/v1.x/design.html 我们还是回归正题。
堵塞
在Node.js中堵塞就是在Node.js进程中执行一个javascript代码必须等到进程中的非javascript操作完成(这句话比较难理解,就是假如同步读取一个文件内容,在文件读取过程,javascript必须等到文件读取完才能执行后面的代码。)。 发生这种问题的原因就是当一个堵塞操作发生的时候,Nodejs进程中的主线程无法运行javascript代码(因为每个Node进程的都有主线程, Javascript代码全在这个单线程下运行)。
在Node.js中,javascript展示了在cup密集型任务的不足点,但是在等待非javascript操作,如调用系统的I/O,JavaScript没有表现出它的不足。 I/O操作并不算称为典型的堵塞操作。
cup密集型如对巨量的数据进行加密解密,对视频流,图形进行处理。至于为什么Node.js在处理cup密集型计算上有先天的缺陷,可以查看Node.js软肋之cpu密集型任务 这篇文章。
Node.js标准库中所有的I/O函数都提供了异步版本。这些都是非堵塞的,可以接受一个回调函数。
堵塞的方法同步的执行,非堵塞的方法异步的执行。现在我们使用文件系统模块作为例子。
下面这是同步的读取文件
const fs = require('fs');
const data = fs.readFileSync('/file.md');//blocks here until file is read
console.log(data);
//moreWork(); will run after console.log
这个是异步的例子
const fs = require('fs');
fs.readFile('/file.md',(err,data)=>{
if(err) throw err;
console.log(data);
})
//moreWork(); will run before console.log
在上面的第一个例子中, console.log 会在moreWork()之前被调用,在第二个例子中 fs.readFile是非堵塞的,所以javascript能够继续执行,并且moreWork会首先调用。 在没有等待文件读取完成,就去运行moreWork() 是提搞吞吐量的关键。
并发和吞吐量
Node.js中的Javascript执行是单线程的,所以并发意味着event loop 完成其他工作之后,执行javascript回调函数。那么我们必须保证当I/O操作发生时,以并发方式运行的代码能够允许事件循环继续运行。
举个例子,我们假设每个发送到webserver的请求需要50ms完成,其中45ms是databse I/O是异步的完成的。选择异步操作,45ms的时间可以处理其请求。这是非堵塞与堵塞的很重要的不同点。
Node.js中的event loop 跟其他语言不同,有些语言使用多线程处理并发的问题。我们可以从十几年前Dan Kegel提出的C10K问题,上面提出对应的处理方案,现在人们又面临的时C10M问题。
将堵塞代码和非堵塞代码混合的害处
当我们处理I/O的时候,我们应该避免产生以下的代码。
const fs = require('fs');
fs.readFile('/file.md',(err,data)=>{
if(err) throw err;
console.log(data);
});
fs.unlinkSync('./file.md');
上面的例子,fs.unlinkSync() 很可能在fs.readFile()之前运行,这样的话很可能在读文件之前就删除了文件。 更好的写法是如下非堵塞的写法。
const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
if (readFileErr) throw readFileErr;
console.log(data);
fs.unlink('/file.md', (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
});
这样就能保证程序的正常运行
参考资料
高性能网络编程(二):上一个10年,著名的C10K并发连接问题
高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了
发表评论