基操勿 6 | Node.js 的异步I/O到底有多秀?

众所周知,JavaScript 作为网页中的执行脚本,通常是由浏览器来解释执行的。自从Node.js 出现后,解释执行 JavaScript 代码的工具又多了一种。


图片


与浏览器不同的是,JavaScript 可以借助 Node.js 提供的功能模块去完成更多事情。比如创建、修改、删除文件;比如发送接收网络信息等等。因此, JavaScript 版本的服务器程序随之诞生,也就有了后来大量的 JavaScript 版本的实用工具。毫不夸张地讲,正是因为有了 Node.js,才有了今天前端的蓬勃发展。


与浏览器相比,Node.js 让 JS 功能更强大、更丰富。在 Node.js 加持下,JS 和 其它编程语言比较,有着与众不同的特点。


典型的说法是,Node.js具有异步I/O、单线程、事件驱动这些特点。其实这三个特点是一回事儿,不过是从三个角度来看待的。


接下来,我们就以『异步I/O』为切入口,来看看 Node.js 的优秀之处。


比如统计一个目录中文件大小总和,过程中要获取每一个文件的大小信息,这就牵扯到非常典型的磁盘I/O操作。


在不考虑多线程的情况下,有两种操作:

  • 同步I/O操作。步骤是读取完一个文件信息,得到大小后, 再读取下一个文件。(它们的关系是阻塞的)

  • 异步I/O操作。读取第一个文件时,不等信息返回,就发送读取第二个文件信息的指令。整个目录中文件信息的获取时间是非线性的。(或者说,读取文件大小这些小任务之间是『非阻塞』的)


PHP版本的代码如下:

function dirSize($dir) {
    $hd = opendir($dir); // 打开目录,获取句柄
    $total = 0; // 初始值
    while(($f = readdir($hd)) !== false) {
           // 跳过.和..
           if ($f === '.' || $f === '..') {continue;}
           // 获取每文件文件大小, 进行累加
           $total += filesize($dir.'/'.$f);
   }
   return $total;
}

echo dirSize('./code');

一些同学可能没有接触过PHP,下面来一个JS版本方便大家理解。


JS同步版本:

const fs = require("fs")

function dirSizeSync(dir) {
  // 1.获取目录中所有文件名
  let files = fs.readdirSync(dir)

  // 2.求每个文件大小,加到总和上, 最后返回
  return files.reduce((totalSize, file) => {
    return totalSize + fs.statSync(`${dir}/${file}`).size
  }, 0)
}

// 测试. 统计 code 目录
console.log(dirSizeSync("./code"))

JS异步版本:

const fs = require("fs").promises // 采用promise风格API

async function dirSizeAsync(dir) {
  // 1.读取目录中所有文件
  let files = await fs.readdir(dir)

  // 2.异步得到所有文件的相关信息, 返回的是一组promise
  let arrPromiseStat = files.map((file) => fs.stat(`${dir}/${file}`))

  // 3.等所有promise都完成后,返回一个结果数组
  let stats = await Promise.all(arrPromiseStat)

  // 4.对结果数组求和,并返回
  return stats.reduce((total, stat) => total + stat.size, 0)
}

// 测试. 统计 ./code 目录
dirSizeAsync("./code").then((total) => {
  console.log(total)
})

从代码量上看,异步版本更多。


但是,代码要跑一跑的:

// 统计同步版本用时
console.time("同步")
console.log(dirSizeSync("./code"))
console.timeEnd("同步")

// 统计异步版本用时
console.time("异步")
dirSizeAsync("./code").then((totalSize) => {
  console.log(totalSize)
  console.timeEnd("异步")
})

来看一下结果:

图片


随着业务量增大,这两者的差距会更大。异步I/O的实现依赖于事件驱动,它使得一个线程能够被充分利用。多线程中处理任务经常要考虑复杂的线程同步问题,所以 Node 被有意设计为单线程,进而也减少了多线程间切换的开销。


值得说明的是,这并不是说 Node 就不可以开启多个线程。详细的原因这里不做过多解释。也正是这样一次练习让我们真正对 Node 重视起来,不再把它当成一个玩具。近期 Node 再次发布了新版本 15.0.1,希望它能给我们带来更多的惊喜。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值