大家好,我是前端理想哥!
今天是我们Node.js学习的第四节课,这节课我们来聊一聊Node.js 中“阻塞(Blocking)”和“非阻塞(Non-Blocking)”的区别,以及它们在实际开发中的影响。让我们一起搞懂如何在单线程环境下,提升吞吐量和并发能力!
一、什么是阻塞与非阻塞
我们先来解释一个概念,什么是阻塞?什么又是非阻塞?
-
阻塞(Blocking)
当需要执行的 JavaScript 逻辑必须等待某个非 JavaScript 操作(如 I/O)完成后,才能继续往下运行,这就叫阻塞。比如
Node.js标准库中带Sync后缀的方法,常常会同步读取或写入文件,一旦调用就会卡住当前线程,直到操作结束才继续执行下一行代码。 -
非阻塞(Non-Blocking)
非阻塞方法在执行时,会让出主线程去做其他事情,等到 I/O 完成后,再通过回调函数(或 Promise)把结果返回。这样就可以在等待期间执行更多任务,不浪费时间。
二、对比示例
接下来,我们看一组对比示例。
我们要实现以下功能:使用Node.js的文件系统模块读取本地 file.md 文件
同步写法:
const fs = require('node:fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read
异步写法:
const fs = require('node:fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
});
以上代码都能实现我们的功能是吧?我们继续再拓展这两个例子
同步写法:
const fs = require('node:fs');
const data = fs.readFileSync('/file.md'); // 阻塞,直到文件读取完毕
moreWork(); // 只有等待上面读取完成后才继续执行
异步写法:
const fs = require('node:fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
moreWork(); // 不等待文件读取,直接执行
看到了吗?
同步的这种写法简单,逻辑直观,但是阻塞期间无法执行其他操作,主线程被“卡住”。
异步的写法能并行处理更多任务,大幅提升吞吐量。但是代码流程看起来不再是从上到下,需要使用回调来处理数据。
这就是两者的区别。
三、正确使用阻塞和非阻塞
对于阻塞和非阻塞,大家在日常开发使用中,一定要正确使用。
举个“危险”示例:
const fs = require('node:fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
fs.unlinkSync('/file.md'); // 同步删除,可能比读取先执行
• 这里 unlinkSync() 很可能会在 readFile() 之前完成,导致文件被删掉了还没读取完。
正确做法:全程非阻塞
const fs = require('node:fs');
fs.readFile('/file.md', (readFileErr, data) => {
if (readFileErr) throw readFileErr;
console.log(data);
fs.unlink('/file.md', (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
});
• 这样保证了读取完再删除,顺序完全可控。
四、如何选择
- 优先使用非阻塞异步方法
• 提高并发,让 Node.js 在等待 I/O 时不会闲置,可以同时处理更多任务。
- 避免不必要的同步调用
• 同步方法(尤其是带 Sync 后缀的)会直接阻塞主线程,不适合在高并发场景中频繁使用。
- 注意操作顺序
• 对操作先后顺序有依赖时,要么使用回调或Promise/async-await来控制流程,要么确保所有相关操作都放在同一个异步链路里执行。
最后:
在 Node.js 里,玩转异步是提升性能与并发的关键。阻塞方法虽写起来简单,却可能让你的应用“卡到爆”;而非阻塞方法让主线程“腾出手”,提升吞吐量和用户体验。
记住:能用异步就别用同步,这是写 Node.js 的不二法门!
如果你觉得这些内容对你有帮助,记得点赞、收藏并分享,如果你有任何问题,欢迎在评论区讨论!
如果你也对 Node.js 感兴趣,记得关注理想哥,我们一起深入探索。