同步异步与阻塞非阻塞这两组概念在 IO 场景下非常常见,由于这两组概念在表现出来的效果上很相似,很容易造成混淆和困扰,要想理清楚这两组概念需要认识到这两组概念强调的是不同维度的事。
同步异步强调的是两个操作之间的顺序关系,两个操作之间是有序的还是无序的;
阻塞与非阻塞强调的是一个调用发起后调用发起方的行为,是被动等待还是主动获得执行权;
下面进一步细化和对比
同步与异步
同步 "Synchronous" 这个词源自希腊语 "syn"(意为"一起")和 "chronos"(意为"时间"),它的字面意思是"在同一时间发生"。在通信和计算机领域中,“同步”则有两层含义,一个是"一起发生",另一个是"按顺序进行",这两层含义缺一不可,它意味着多个操作按照预定的顺序和时间协调进行,从而保持整体的一致性和协调性。
这里可以联想一下并发控制中为什么存在“同步互斥”这样的概念?目的就是为了协调多进程访问临界区时,必须等临界区中的 A 进程退出临界区后,B 进程才可以进入临界区执行,本质上是将并行关系变成了串行关系。
再回想一下 SQL 隔离级别中最高级串行化 Serializable 是不是更能理解了?同样是将并行关系变成串行关系。
同步 (Synchronous)
同步指的是某个操作 A 必须等待前一个操作 B 完成之后才能开始,也就是说 A 在 B 完成之前不会启动。
也可以描述为 A sync before B,意味着操作 A 在操作 B 之后按顺序执行,并且 A 必须等待 B 完成后才开始。
说白了同步意味着 A 和 B 之间的执行有先后顺序关系,中国有句古话:先穿袜子再穿鞋,先当孙子再当爷,讲述的就是这个道理。
同步例子,其中 task_A
和 task_B
是同步关系,只有 task_A
执行完了task_B
才能执行。
输出
异步 (Asynchronous)
在异步操作中,操作 A 不需要等待前一个操作 B 完成之后才能开始,A 和 B 可以同时进行,或者 A 可以在等待 B 的过程中执行其他操作。
可以描述为 A async with B 意味着操作 A 和操作 B 可以同时执行或 A 不需要等待 B 完成。
说白了 A 和 B 的执行没半毛钱关系,你在穿鞋的同时也可以喘气儿,先喘再穿还是先穿再喘甚至边穿边喘都可以,怎么喜欢怎么来,互不影响。
异步例子,task_A
和 task_B
同时执行,都不需要等待对方,各自爱怎么跑怎么跑。
输出
阻塞调用与非阻塞调用
阻塞和非阻塞重点强调的是调用方在发出调用后的行为,为了更好的理解这一对儿概念,可以在阻塞和非阻塞后面加上“调用”俩字,变成阻塞调用和非阻塞调用。
阻塞调用 (Blocking)
阻塞调用发出后,调用方会挂起等待,当被调用方执行完成并返回结果后,调用方才会被唤醒并接到结果继续执行之后的操作。
说白了阻塞调用就是发出调用后傻等着,整个进程都等在调用发出这一行。
代码示例,下面代码中 blocking_operation
内部有一个耗时操作,main
函数中进行阻塞调用,blocking_operation
不返回就一直在这等。
输出
非阻塞调用 (Non-blocking)
非阻塞调用发出后,调用方不会挂起等待,而是立即返回,之后可以选择继续别的操作。被调用方在后台(可能以各种形式实现)处理原本的业务逻辑,处理完成后可以通过回调、信号等机制通知调用方。
说白了非阻塞调用就是发出调用后马上返回,无论能不能得到想要结果都义无反顾的返回,啪的一下很快啊。至于结果没拿到怎么办?可以循环重试啊。
代码示例,下面代码中 non_blocking_operation
中有一个耗时操作,但调用时以非阻塞方式调用,立刻返回并继续执行 main
函数后面内容而不是一直等待。
输出
两两结合
现在说说这两组概念的两两结合,设想这样一个场景,在一个主流程 main
中希望调用 read
发起 IO 读取数据,根据 main
和 read
的顺序关系以及 main
发出调用后的状态可分为如下几种情况:
同步阻塞
同步意味着 main
只有在 read
完成后才能继续执行,同步意味着有序;
阻塞意味着只要 read
不返回则 main
就必须挂起等待。下面是一段示例:
输出
同步非阻塞
首先说结论这种模式很少有实际应用。
同步意味着 main
只有在 read
完成后才能继续执行,同步意味着有序;
非阻塞意味着 read
调用会马上返回所以 main
可以立刻获得 CPU 时间片得以继续执行,但由于 main
和 read
之间是同步关系,main
必须等待 read
真正完成后才能继续执行,那么 main
只能主动放弃执行进而等待类似回调机制的通知。
因为 main
已经获得了执行权但却又不真正执行,等同于浪费了 CPU 的调度和时间片,所以这种情况在实际应用中很少就不写例子了(实际上我没想到有什么典型的例子可以写)。
异步阻塞
首先还是说结论这种模式的应用也非常少。
异步意味着 main
与 read
的执行互不影响,相互之间并不存在谁要等谁的情况,可以各自愉快滴运行,异步意味着无序。
阻塞意味着 main
调用 read
后必须等待 read
的结果返回,实际上这也浪费了 main
和 read
之间的异步关系,本可以并行执行的,现在只能挂起等待,所以实际应用并不多,也没有特别好的例子可写的。
异步非阻塞
异步意味着 main
与 read
的执行互不影响,相互之间并不存在谁要等谁的情况,可以各自愉快滴运行,异步意味着无序。
非阻塞意味着 read
调用后可以马上返回,同时由于二者是异步关系,所以可以实现 main
和 read
各自都可以继续向下执行,并发效率是最高的。
输出
总结
想要彻底搞清楚同步和异步、阻塞和非阻塞,就要明确他们分别是从两个维度出发强调的不同概念。前者强调的是两个操作之间的顺序关系,后者强调的是调用方发出调用后的行为,搞清楚这两个维度才能够清晰的理清楚他们之间的关系。