# Rust async 編程 Asynchronous Programming in Rust: 中文書名《Rust 非同步編程指南》: Rust語言聖經(Rust Course): ## 一、[Getting Started](https://rust-lang.github.io/async-b ...
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 社區提供,例如
tokio
- 運行時由Rust 社區提供,例如
- 單線程、多線程均支持
- 這兩者擁有各自的優缺點
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 代碼間也不總是能自由組合
- 一些crate依賴於特定的
async
運行時
- 一些crate依賴於特定的
-
因此,儘早研究確定使用哪個 async 運行時
性能特征
async
的性能依賴於運行時的表現(通常較出色)
1.3 async/await 入門
async
async
把一段代碼轉化為一個實現了Future trait
的狀態機- 雖然在同步方法中調用阻塞函數會阻塞整個線程,但阻塞的Future將放棄對線程的控制,從而允許其它
Future
來運行。
~/rust via