zoukankan      html  css  js  c++  java
  • Node.js官方文档:到底什么是阻塞(Blocking)与非阻塞(Non-Blocking)?

    译者按: Node.js文档阅读系列之一。

    为了保证可读性,本文采用意译而非直译。

    这篇博客将介绍Node.js的阻塞(Blocking)与非阻塞(Non-Blocking)。我会提到Event Loop与libuv,但是不了解它们也不会影响阅读。读者只需要有一定的JavaScript基础,理解Node.js的回调函数(callback pattern)就可以了。

    博客中提到了很多次I/O,它主要指的是使用libuv与系统的磁盘与网络进行交互。

    阻塞(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以及本文地址:
    https://blog.fundebug.com/2019/06/12/overview-of-nodejs-blocking-vs-non-blocking/

  • 相关阅读:
    LeetCode:Remove Nth Node From End of List
    链表排序(冒泡、选择、插入、快排、归并、希尔、堆排序)
    快速排序partition过程常见的两种写法+快速排序非递归实现
    LeetCode:Permutations, Permutations II(求全排列)
    LeetCode:3Sum, 3Sum Closest, 4Sum
    LeetCode:Two Sum
    判断一个图是否有环
    c++设计一个无法被继承的类
    设计模式--单例模式
    windows多线程同步互斥--总结
  • 原文地址:https://www.cnblogs.com/fundebug/p/overview-of-nodejs-blocking-vs-non-blocking.html
Copyright © 2011-2022 走看看