Nodejs 中的同步与异步、阻塞与非阻塞

最近在微信读书上读一本国内讲Nginx的书,里面提到异步,同步,阻塞和非阻塞,然后里面的用户笔记乱解释,还一大堆人给他点爱心,所以现在连辩解错误的能力都没有了么?

在学习Nodejs的过程中,经常会听到同步异步,阻塞和非阻塞。那什么是异步什么是同步呢?什么是阻塞和非阻塞?

Nodejs是单线程的异步事件驱动的Js运行时,使用事件循环的方式,将操作交给系统内核处理,当操作完成后内核通知Nodejs讲合适的回调函数添加到轮询队列中等待回调执行。而阻塞,在官方教程中也有说明

阻塞 是指在 Node.js 程序中,其它 JavaScript 语句的执行,必须等待一个非 JavaScript 操作完成。这是因为当 阻塞 发生时,事件循环无法继续运行 JavaScript。

在 Node.js 中,JavaScript 由于执行 CPU 密集型操作,而不是等待一个非 JavaScript 操作(例如 I/O)而表现不佳,通常不被称为 阻塞。在 Node.js 标准库中使用 libuv 的同步方法是最常用的 阻塞 操作。原生模块中也有 阻塞 方法。

在 Node.js 标准库中的所有 I/O 方法都提供异步版本,非阻塞,并且接受回调函数。某些方法也有对应的 阻塞 版本,名字以 Sync 结尾。

阻塞对比非阻塞一览

很明显可以看出,所谓的非 JavaScript 操作既不是ES中规定的API操作。所以像for循环,数值计算,这种并不算是阻塞。而标准库中所有的Sync方法是非 JavaScript 操作操作,会阻塞主线程的JS代码运行。

在Nodejs中,我们常听到异步回调,到底什么是异步回调?在js中,回调是通过执行传入的方法,既等到操作执行完,会通过回调方法通知。既然有异步回调也会有同步回调。

1
2
3
4
5
6
7
8
9
10
11
function func1(num, callbacll = result => result) {
callbacll(num ** 2);
}
console.log("start");
func1(2, res => {
console.log(res);
});
console.log("end");
// start
// 4
// end

同步既执行顺序与代码编写顺序相符的。而异步则是执行顺序与代码编写顺序不符。但是同步回调可直接写成下面这样子,所以一般同步回调使用的场景几乎没有。

1
2
3
4
5
6
7
8
9
function func1(num) {
return num ** 2;
}
console.log("start");
console.log(func1(2));
console.log("end");
// start
// 4
// end

那看看异步回调是怎么样的。

1
2
3
4
5
6
7
8
9
10
11
12
const fs = require("fs");
console.log("start");
fs.readFile("./1.js", (err, data) => {
if (err) {
throw err;
}
console.log("data");
});
console.log("end");
// start
// end
// data

其中readFile的第二个参数就是异步回调方法。虽然叫异步回调方法,但是也是在主线程执行的,毕竟事件轮询调用这个回调方法是用来通知主线程的。

非阻塞方法需要异步回调方法通知主线程,阻塞方法直接阻塞Js主线程。阻塞非阻塞都为非 JavaScript 操作JavaScript 操作不能成为阻塞非阻塞

因为非阻塞需要异步回调函数通知主线程,当一个操作流程需要一连串的IO和网络操作,并且这些IO和网络操作都需要严格按照一定执行时,就会出现回调地狱。为了解决这个问题,人们给异步回调披上Promise的外衣,实现了链式调用,把异步回调用then的方式执行,把多行代码压成一行。但是依然不如同步代码一样看起来直观,所以人们有给Promise装一个箱子,就是asyncawait

参考资料


Nodejs 中的同步与异步、阻塞与非阻塞
https://bubao.github.io/posts/1a87a4b.html
作者
一念
发布于
2021年11月21日
许可协议