Rust async 编程
Asynchronous Programming in Rust:https://rust-lang.github.io/async-book/
中文书名《Rust 异步编程指南》:https://github.com/rustlang-cn/async-book
Rust语言圣经(Rust Course):https://course.rs/advance/async/getting-started.html
一、Getting Started
1.1 为什么使用 async
为什么使用 async
- Async 编程,是一种并发(concurrent)编程模型
- 允许你在少数系统线程上运行大量的并发任务
- 通过 async/await 语法,看起来和同步编程差不多
其它的并发模型
- OS 线程
- 无需改变任何编程模型,线程间同步困难,性能开销大
- 线程池可以降低一些成本,但难以支撑大量 IO 绑定的工作
- Event-driven 编程
- 与回调函数一起用,可能高效
- 非线性的控制流,数据流和错误传播难以追踪
- 协程(Coroutines)
- 类似线程,无需改变编程模型
- 类似 async ,支持大量任务
- 抽象掉了底层细节(这对系统编程、自定义运行时的实现很重要)
- Actor 模型
- 将所有并发计算划分为 actor , 消息通信易出错
- 可以有效的实现 actor 模型,但许多实际问题没解决(例如流程控制、重试逻辑)
Rust 中的 async
- Future 是惰性的
- 只有poll时才能取得进展, 被丢弃的 future 就无法取得进展了
- Async是零成本的
- 使用async ,可以无需堆内存分配(heap allocation)和动态调度(dynamic dispatch),对性能大好,且允许在受限环境使用 async
- 不提供内置运行时
- 单线程、多线程均支持
Rust 中的 async 和线程(thread)
- OS 线程:
- 适用于少量任务,有内存和CPU开销,且线程生成和线程间切换非常昂贵
- 线程池可以降低一些成本
- 允许重用同步代码,代码无需大改,无需特定编程模型
- 有些系统支持修改线程优先级
- Async:
- 显著降低内存和CPU开销
- 同等条件下,支持比线程多几个数量级的任务(少数线程支撑大量任务)
- 可执行文件大(需要生成状态机,每个可执行文件捆绑一个异步运行时)
Async 并不是比线程好,只是不同而已!
总结:
- 有大量 IO 任务需要并发运行时,选 async 模型
- 有部分 IO 任务需要并发运行时,选多线程,如果想要降低线程创建和销毁的开销,可以使用线程池
- 有大量 CPU 密集任务需要并行运行时,例如并行计算,选多线程模型,且让线程数等于或者稍大于 CPU 核心数
- 无所谓时,统一选多线程
例子
如果想并发的下载文件,你可以使用多线程如下实现:- fn get_two_sites() {
- // Spawn two threads to do work. 创建两个新线程执行任务
- let thread_one = thread::spawn(|| download("https://www.foo.com"));
- let thread_two = thread::spawn(|| download("https://www.bar.com"));
- // Wait for both threads to complete. 等待两个线程的完成
- thread_one.join().expect("thread one panicked");
- thread_two.join().expect("thread two panicked");
- }
复制代码 使用async的方式:- async fn get_two_sites_async() {
- // Create two different "futures" which, when run to completion, 创建两个不同的`future`,你可以把`future`理解为未来某个时刻会被执行的计划任务
- // will asynchronously download the webpages. 当两个`future`被同时执行后,它们将并发的去下载目标页面
- let future_one = download_async("https://www.foo.com");
- let future_two = download_async("https://www.bar.com");
- // Run both futures to completion at the same time. 同时运行两个`future`,直至完成
- join!(future_one, future_two);
- }
复制代码 自定义并发模型
- 除了线程和async,还可以用其它的并发模型(例如 event-driven)
1.2 Rust Async 的目前状态
Async Rust 目前的状态
- 部分稳定,部分仍在变化。
- 特点:
- 针对典型并发任务,性能出色
- 与高级语言特性频繁交互(生命周期、pinning)
- 同步和异步代码间、不同运行时的异步代码间存在兼容性约束
- 由于不断进化,维护负担更重
语言和库的支持
- 虽然Rust本身就支持Async编程,但很多应用依赖与社区的库:
- 标准库提供了最基本的特性、类型和功能,例如 Future trait
- async/await 语法直接被Rust编译器支持
- futures crate 提供了许多实用类型、宏和函数。它们可以用于任何异步应用程序。
- 异步代码、IO 和任务生成的执行由 "async runtimes" 提供,例如 Tokio 和 async-std。大多数async 应用程序和一些 async crate 都依赖于特定的运行时。
注意
- Rust 不允许你在 trait 里声明 async 函数
编译和调试
- 编译错误:
- 由于 async 通常依赖于更复杂的语言功能,例如生命周期和Pinning,因此可能会更频繁地遇到这些类型的错误。
- 运行时错误:
- 每当运行时遇到异步函数,编译器会在后台生成一个状态机,Stack traces 里有其明细,以及运行时调用的函数。因此解释起来更复杂。
- 新的失效模式:
- 可能出现一些新的故障,它们可以通过编译,甚至单元测试。
兼容性考虑
- async和同步代码不能总是自由组合
- 例如,不能直接从同步函数来调用 async 异步函数
- Async 代码间也不总是能自由组合
- 因此,尽早研究确定使用哪个 async 运行时
性能特征
- async 的性能依赖于运行时的表现(通常较出色)
1.3 async/await 入门
async
- async 把一段代码转化为一个实现了Future trait 的状态机
- 虽然在同步方法中调用阻塞函数会阻塞整个线程,但阻塞的Future将放弃对线程的控制,从而允许其它Future来运行。
[code]~/rust via
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |