Skip to content

介绍

什么是异步编程?

简单来说, 异步编程是一种编程范式, 它允许程序在等待一个耗时操作(如网络请求、文件读写)完成的同时, 继续执行其他任务, 而不是“傻等”着结果回来

  • 同步(你做饭,等水开): 把水壶放在灶上, 然后站在那里盯着水壶, 直到水烧开, 你才去切菜。这期间你什么事都做不了。这就是 阻塞
  • 异步(你做饭,水开了告诉我):把水壶放在灶上, 按下烧水开关, 然后转身去切菜、洗锅。水烧开后, 水壶发出“哔哔”的提示音(回调), 你放下菜刀去关闭开关。这期间你充分利用了时间。这就是非阻塞

基础概念

  • 并行(parallelism): 同时执行多个操作(比如多线程/多进程)
  • 并发(concurrency): 快速切换操作(如: JS 的 Event Loop)
  • 串行(Serial work): 任务按照特定顺序, 一个接一个的完成
  • 阻塞(Blocking): 阻止程序的后续执行, 直到某个操作完成
  • 非阻塞(Non-blocking): 在程序执行时, 如果某个操作没有完成, 就不会阻塞程序后续执行
  • 回调函数(Callback): 把 水开后要做什么 作为函数传入, 等操作完成时自动调用, 这是最基础的形式, 但容易形成callback hell(层层嵌套, 代码难以维护)
  • 事件循环(EventLoop): JavaScript 和 Python 的 asyncio 都依赖这个。它像一个一直运行的"调度器", 不断检查是否有“烧开了”的事件, 如果有就把对应的任务丢给CPU去执行
  • Promise/Future: 这是一个承诺, 它代表一个未来才会完成的操作, 你可以先拿到这个凭证, 等结果准备好后, 再通过 await 去拿实际数据

程序操作类型

  • CPU 密集型(CPU-bound): 如导出视频(需要进行大量计算)
  • IO 密集型(IO-bound): 如大量网络请求/读写文件

学习资料

异步编程模型

  • OS 多线程/多进程

    • 无需改变编程模型, 线程间同步困难, 性能开销大
    • 具体应用: 比如之前写多线程 web 服务器时学过的 线程池
  • Event-deriven: 事件驱动

    • 类似 nodejs 的那种回调函数, 虽然高效, 但由于是非线性控制流, 所以可能会出现 callback hell 问题
    • 由于是非线性控制的, 所以可能会难以定位和传播错误
    • 如 NodeJS 的 fs.readFile API
  • Coroutine: 微线程(有的也叫 纤程 协程)

    • 类似OS多线程, 但是由程序控制的, 所以没有线程切换的开销, 所以执行效率高, 类似 concurrent
    • 一般由编程语言提供的API, 比如: python 的 yield/asyncio, JS 的 Promise API
  • GMP: Go语言的并发模型

  • Actor 模型: 暂时还未学习过, 先过

Rust 中的 async/await

  • Future 是惰性的
  • async 是零成本抽象(无需分配多内存和动态调度)
  • 不提供标准库实现, 由社区支持, 如 tokioasync-std

多线程与 async/await 的关系

  • 多线程

    • 没有额外的运行时, 允许重复利用现有代码,代码几乎无需修改即可获得异步的能力(如之前写多线程web服务器的例子)
    • 创建/销毁线程内存和CPU开销大(线程池只能降低一些成本)
  • async/await

    • 内存和CPU开销小, 对比多线程可显著降低内存和CPU开销(大量任务的情况下)
    • 需要额外的运行时来生产状态机, 可执行文件大

async/await 这种并发和多线程各有不同的应用场景, 不代表 async/await 就一定比多线程并发更好

就比如你需要锻炼身体, 玩篮球和玩排球的关系, 都可以锻炼身体, 只是玩法不同, 没有哪种更好

语言和库的支持

  • rust 标准库只提供了最基本的特性/类型/和功能, 比如 Future trait
  • rust 编译器直接支持 async/await 语法
  • 异步代码/io和任务申城的执行由 async runtimes 提供

由于 rust 标准库只提供了最基本的功能和语法支持, 虽然可以实现并发编程, 但是不够方便好用, 所以很多的引用都依赖于社区实现的库, 如: async-stdtokio

注意

rust 不允许在 trait 中声明 async 函数

兼容性考虑

  • async 和同步代码不能总是自由结合(如: 不能直接从同步函数中调用异步函数)

  • async 代码也不总是能只有结合(如: 因为不同的实现依赖于不同的runtime, 比如 async-std 和 rayon 就不能混着用)

Released under the MIT License.