async/await机制(最初由C#在2012年开创,随后被JavaScript(ES2017)、Python(3.5)、Rust(1.39)、Kotlin、Swift和Dart采用。业内迅速采纳了这一模式,JavaScript框架全面支持,Python的asyncio成为并发I/O的标准方式,而Rust则稳定了async/await作为高性能网络编程的路径。短短几年间,async/await便成为多数主流语言中编写并发I/O代码的默认方式
优点:让异步代码看起来像顺序执行
Paying the Function Coloring Tax:鲍勃·奈斯特罗姆发表了《你的函数是什么颜色?》一文。这篇文章设想了一门语言,其中每个函数要么是“红色”,要么是“蓝色”。红色函数可以调用蓝色函数,但蓝色函数要调用红色函数则必须经过特殊仪式。每个函数都必须选择一种颜色,如果你从蓝色函数中调用了红色函数,那么该蓝色函数就必须变成红色——这种特性会像病毒般在整个代码库中蔓延
这是对async/await机制的一个类比:异步函数是红色,同步函数是蓝色。异步函数调用同步函数毫无问题,但若要从同步函数中调用异步函数,则必须阻塞线程或重构代码。程序中每个函数都必须选择一种颜色,而这一选择会传播至每个调用者。
缺点:函数类型分裂:同步函数与异步函数,调用者需要依此区分设计模式与使用。一个同步思维设计模式下的项目,一旦启用了一个异步库,意味着一长串函数的修改。
缺点:库的割裂:库和接口的提供者需要决定使用同步还是异步。在Python中,requests库(同步)和aiohttp(异步)是由不同开发者独立维护的两个项目,功能却完全相同。httpx最终实现了从同一个包中同时提供两种接口,然而这种改进之所以必要,正是由于同步与异步的人为割裂。
缺点:新型 bug。O'Connor 记录了一种异步 Rust 的死锁类型,他称之为“futurelock”:一个 future 持有锁后停止轮询,而另一个 future 试图获取同一把锁。在线程模型中,持有锁的线程总会继续执行直到释放锁(除非你做了像 SuspendThread 这样人人皆知危险的操作)。而在异步 Rust 中,select!、缓冲流以及 FuturesUnordered 等标准工具会频繁停止轮询那些持有资源的 future。Oxide 公司最初遇到的 futurelock 问题,需要通过核心转储和反汇编器才能诊断出来
缺点:顺序陷阱:与 Promise 相比,Promise 要求你分析业务逻辑的依赖、并发与串行再组合代码,而 async 容易掉入串行认知陷阱:把两个独立可并发的异步动作写成了串行,损失了性能。