同步 vs 异步

Wiki:异步 I/O

In computer science, asynchronous I/O (also non-sequential I/O) is a form of input/output processing that permits other processing to continue before the transmission has finished.

在计算机科学中,异步I/O(也叫非顺序I/O)是一种输入/输出处理形式,允许其他处理在它传输完成之前继续。

这个定义同样可以适用到所有的同步、异步。
整理一下信息我们可以得出:

  • 同步或者异步是一种处理形式
  • 同步是顺序的;异步是非顺序的。
  • 同步操作在处理完成之前不允许进行其他操作;异步操作在传输完成之前允许进行其他操作。

同步或者异步是一种处理形式

同步或者异步是用来描述两个任务(操作)的,是相对的概念。我们常说的一个任务是同步的或者异步的,是相对于另一个任务的。同步或异步描述的如何处理两个任务的执行顺序,所以说是一种处理形式

同步是顺序的;异步是非顺序的

任务 B 的执行需要任务 A 执行的结果,j任务 C 的执行需要任务 B 执行的结果。因此任务 B 只能等待任务 A 执行完毕才能执行,任务 C 只有在任务 B 执行完毕之后才能执行,所以最终的顺序只能是 A -> B -> C。也可以说,同步执行的任务是有关联、有依赖的,相反,异步执行的任务互相独立。

同步的操作在处理完成之前不允许进行其他操作;异步操作在传输完成之前允许进行其他操作

同步操作的其他操作(也可以说是进一步操作)指的就是同步的其他任务。同步的任务由于存在依赖关系,所以在被依赖的任务完成之前是没有办法进行其他任务的;而异步的任务由于没有依赖关系,所以完全可以并发执行,两个完全不相干的任务没有必要去等彼此。

用图来表示:

同步的任务 A、B、C:

1
2
3
4
5
thread A -> |<---A---->|   
\
thread B ------------> ->|<----B---------->|
\
thread C ----------------------------------> ->|<------C----->|

A、B、C 分别在不同的线程上,但是由于其同步的关系,所以 B、C 在一开始并不会运行,而是按照 A -> B -> C 的顺序执行的。

异步的任务 A、B、C:

1
2
3
thread A ->     |<---A---->|
thread B -----> |<----B---------->|
thread C ---------> |<------C--------->|

异步的任务 A、B、C 是完全是由自己决定运行的。

图示来源:stackoverflow

<>代表任务的开始与结束;|代表CPU时间片

关于同步与异步的生动的比喻:

同步:你在排队买电影票,只有你前面的所有人都买到电影票你才能买,你和其他人是同步的。
异步:你在餐馆点餐,餐厅有很多服务员。你点你的餐,其他人也在点他们的餐。他们并不需要等你取到餐才能点餐。你与其他人是异步的。

阻塞 vs 非阻塞

首先,阻塞和非阻塞对应的英文是 blocking 和 non-blocking,而词典上是没有这两个词的。我们只能认为他们是 block 的一种形态。从单词上可以看出 blocking 可以是名词和形容词。

Wiki 上关于 非阻塞(non-blocking) 的定义

In computer science, an algorithm is called non-blocking if failure or suspension of any thread cannot cause failure or suspension of another thread

在计算机科学中,如果任何线程的失败或暂停不会导致另一个线程的失败或暂停,则称这个算法为非阻塞的

Nodejs 官方给出的阻塞的定义

Blocking is when the execution of additional JavaScript in the Node.js process must wait until a non-JavaScript operation completes. This happens because the event loop is unable to continue running JavaScript while a blocking operation is occurring.

阻塞是指在Node.js进程中执行其他JavaScript必须等到非JavaScript操作完成的情况。 发生这种情况是因为事件循环在阻塞操作发生时无法继续运行JavaScript。

从 Wiki 和 Nodejs 上可以看出

  • 阻塞描述的是一种算法,或者叫操作、指令……我们可以说这个操作是阻塞的或者说这是一个阻塞的操作、或者说这个操作是阻塞的。

  • 阻塞操作造成的结果就是导致另一个操作无法继续进行;相反地,非阻塞的操作并不会影响另一个操作的运行。

举例:

1
2
3
4
5
6
7
8
9
// Blocking: 1,... 2
alert(1);
var value = localStorage.getItem('foo');
alert(2);

// Non-blocking: 1, 3,... 2
alert(1);
fetch('example.com').then(() => alert(2));
alert(3);

localStorage.getItem('foo')是阻塞的操作,因此在它执行完之前,alert(2)不会执行;fetch('example.com')是非阻塞的操作,所以不会影响alert(3)的执行。

异步 vs 非阻塞

异步和非阻塞有什么区别?异步等于非阻塞?

这个要视情况而定。

在 JavaScript 中异步和非阻塞导致的结果是一样的——操作 A 不会影响操作 B 的执行,区别在于描述的对象不同,也就是说用法不同。异步是描述两个或多个操作的;非阻塞是描述一个操作的,操作 A 是非阻塞的。非阻塞虽然是描述一个操作,但却是相对于另一个操作的,所以在 JavaScript 中,通常将异步和非阻塞视为同一个东西,在用法上也不严格。

但是在某些领域异步和非阻塞区别很大,例如,在经典的 socket API中,非阻塞 socket 会立即返回一个“would block”的错误消息,而阻塞 socket 会被阻塞。你必须使用单独的功能(例如 select 或 poll)来确定什么时候进行重试。 但异步 socket (由Windows socket 支持)或 .NET 中使用的异步 IO 模式更方便。你调用一个方法来启动一个操作,框架会在完成后进行回调。 ——示例来自 Stack Overflow

异步的实现原理

JavaScript 中异步的实现:在浏览器中,虽然 JavaScript 是单线程的,但是浏览器却不是单线程的。当你发出异步请求时,浏览器会将异步的操作交给 I/O 线程去处理,从而不阻塞 JS 线程的运行。当 I/O 线程运行操作拿到结果后,就会通知 JS 线程,JS 线程就会将回调函数放到任务队列中,至于什么时候调用,就要交给事件循环机制去判断了。Nodejs 也是同理,只不过 Nodejs 会有一个线程池来处理 I/O。

前端常见误解

  • I/O 都是阻塞的?并不是所有 I/O 都是阻塞的,只是我们常见的 I/O 都是阻塞的。

参考: