Node.js官方文档:到底什么是阻塞(Blocking)与非阻塞(Non-Blocking)?
译者按: node.js文档阅读系列之一。
为了保证可读性,本文采用意译而非直译。
这篇博客将介绍node.js的阻塞(blocking)与非阻塞(non-blocking)。我会提到event loop与libuv,但是不了解它们也不会影响阅读。读者只需要有一定的javascript基础,理解node.js的回调函数()就可以了。
博客中提到了很多次i/o,它主要指的是使用与系统的磁盘与网络进行交互。
阻塞(blocking)
阻塞指的是一部分node.js代码需要等到一些非node.js代码执行完成之后才能继续执行。这是因为当阻塞发生时,event loop无法继续执行。
对于node.js来说,由于cpu密集的操作导致代码性能很差时,不能称为阻塞。当需要等待非node.js代码执行时,才能称为阻塞。node.js中依赖于libuv的同步方法(以sync结尾)导致阻塞,是最常见的情况。当然,一些不依赖于libuv的原生node.js方法有些也能导致阻塞。
node.js中所有与i/o相关的方法都提供了异步版本,它们是非阻塞的,可以指定回调函数,例如fs.readfile。其中一些方法也有对应的阻塞版本,它们的函数名以sync结尾,例如fs.readfilesync。
代码示例
阻塞的方法是同步执行的,而非阻塞的方法是异步执行。
以读文件为例,下面是同步执行的代码:
const fs = require('fs'); const data = fs.readfilesync('/file.md'); // 文件读取完成之前,代码会阻塞,不会执行后面的代码 console.log("hello, fundebug!"); // 文件读取完成之后才会打印
对应的异步代码如下:
const fs = require('fs'); fs.readfile('/file.md', (err, data) => { if (err) throw err; }); // 代码不会因为读文件阻塞,会继续执行后面的代码 console.log("hello, fundebug!"); // 文件读完之前就会打印
第一个示例代码看起来要简单很多,但是它的缺点是会阻塞代码执行,后面的代码需要等到整个文件读取完成之后才能继续执行。
在同步代码中,如果读取文件出错了,则错误需要使用try...catch处理,否则进程会崩溃。对于异步代码,是否处理回调函数的错误则取决于开发者。
我们可以将示例代码稍微修改一下,下面是同步代码:
const fs = require('fs'); const data = fs.readfilesync('/file.md'); console.log(data); morework(); // console.log之后再执行
异步代码如下:
const fs = require('fs'); fs.readfile('/file.md', (err, data) => { if (err) throw err; console.log(data); }); morework(); // 先于console.log执行
在第一个示例中,console.log
将会先于morework()
执行。在第二个示例中,由于fs.readfile()
是非阻塞的,代码可以继续执行,因此morework()
会先于console.log
执行。
morework()
不用等待读取整个文件,可以继续执行,这是node.js可以增加吞吐量的关键。
并发与吞吐量
node.js中js代码执行是单线程的,因此并发指的是event loop可以在执行其他代码之后再去执行回调函数。如果希望代码可以并发执行,则所有非javascript代码比如i/o执行时,必须保证event loop继续运行。
举个例子,假设web服务器的每个请求需要50ms完成,其中45ms是数据库的i/o操作。如果使用非阻塞的异步方式执行数据库i/o的话,则可以节省45ms来处理其他请求,这可以极大地提高系统的吞吐量。
event loop这种方式与其他许多语言都不一样,通常它们会创建新的线程来处理并发。
混用阻塞与非阻塞代码会出问题
当我们处理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()
之前执行,也就是说,我们在读取file.md
之前,这个文件就已经被删掉了。
为了避免这种情况,我们应该是要非阻塞方式,来保证它们按照正确的顺序执行。
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; }); });
上面的示例中,我们把非阻塞的fs.unlink()
放在fs.readfile()
的回调函数中。
参考
关于fundebug
fundebug专注于javascript、微信小程序、微信小游戏、支付宝小程序、react native、node.js和java线上应用实时bug监控。 自从2016年双十一正式上线,fundebug累计处理了10亿+错误事件,付费客户有google、360、金山软件、百姓网等众多品牌企业。欢迎大家!
版权声明
转载时请注明作者fundebug以及本文地址:
上一篇: MySQL 添加索引,删除索引及其用法
下一篇: Python正则表达式中re.S作用