非同步程式設計(Asynchronous programming) 或是簡單的稱之為 async,它是一種並發程式模型(concurrent programming model),其目的就是讓多個任務能同時在作業系統的執行緒上執行,並透過 async/.await
保留同步。
Sync 與 Async 簡單的分別:
根據不同程式語言所支持的並發程式模型,以下列出幾種常見的幾種:
callback
,高效率但語法很容易過冗,而且很難追蹤資料流與錯誤的問題actor
的單元,並透過 message
溝通,很像分散式系統。不過有很多問題待解決,例如:流量控制、邏輯問題與上述做比較,在 Rust 等低階程式語言裡,使用非同步程式設計可以實現高性能並同時提供 threads 與 coroutines 的各式優點。
如果在 Rust 使用 OS threads,可以使用 std::threads
或間接訪問 threads pool。
OS threads 適合少量任務,執行緒會使用到 CPU 與記憶體,生成 threads 和在 threads 之間切換是很耗效能的,連空閒的 threads 也會消耗效能,雖然使用 threads pool 可以減輕一些使用成本,但也不是全部。
Threads 能讓你重複使用已存在的同步程式碼,而不需要大幅的改變程式,也不需要特定的設計模型,在一些特定的 OS 中,你還可以設定 threads 的優先級來決定誰先執行。
Async 明顯的減少 CPU 與記憶體的消耗,特別是對於具有大量密集的 IO 工作,例如:伺服器和資料庫。在同等的條件下,使用 async 可以處理比 OS threads 更多的任務,這是因為 async 使用了少量的 threads 來處理大量的簡單任務。
最後要註明一點,並不是 async 比 threads 好,而是要根據當下的狀況來選擇使用,如果你不需要 async 的功能,直接使用 threads 反而是更簡單的方式。
建立兩個 threads 來同時下載兩個網頁:
fn get_two_sites() {
// 產生兩個 threads
let thread_one = thread::spawn(|| download("https://www.foo.com"));
let thread_two = thread::spawn(|| download("https://www.bar.com"));
// 等待 threads 完成工作
thread_one.join().expect("thread one panicked");
thread_two.join().expect("thread two panicked");
}
不過這種簡單的任務建立兩個 threads 是很浪費的,在大型的應用程式中每一點資源都是很重要的,所以在這裡,我們可以選擇 Rust 中的 async 方式,既不需要使用額外的執行緒,還可以達成並發的功能:
async fn get_two_sites_async() {
// 建立兩個不同的 futures,當運行程式會使用 async 的方式來下載
let future_one = download_async("https://www.foo.com");
let future_two = download_async("https://www.bar.com");
// 同時執行兩個 futures
join!(future_one, future_two);
}
這樣寫的話就不會產生額外的 threads,當然唯一的重點就是要自己完成 download_async
的非同步程式碼。
async/.await
是 Rust 內建將非同步編寫成像同步程式碼的方式,async
會將這個指定的區塊轉成一個狀態機制(state machine),稱為 Future
。
Future 是 Rust 裡非同步(Async) 任務的狀態:
這裡使用教學裡的 futures 來實現非同步程式碼,首先新增依賴項到 Cargo.toml
:
[dependencies]
futures = "0.3"
接著在更改 src/main.rs
:
// `block_on` 會阻擋目前的 thread 直到 future 完成
use futures::executor::block_on;
async fn hello_world() {
println!("hello, world!");
}
fn main() {
let future = hello_world();
block_on(future);
}
這邊來更改使用 async/.await
來寫:
use futures::executor::block_on;
use std::{thread, time::Duration};
async fn read_book() {
// 設定一個等待時間
thread::sleep(Duration::from_secs(1));
}
async fn tell_story() {
// 等待完成後才會繼續
read_book().await;
println!("Book content");
}
async fn hand_move() {
println!("Hand move");
}
async fn async_main() {
let f1 = tell_story();
let f2 = hand_move();
// `join!` 類似於 `.await` 可以一次等待多個 futures
futures::join!(f1, f2);
}
fn main() {
block_on(async_main());
}
根據以上的例子,要先讀一本書的內容才能開始說故事,在說故事的期間同時還會有一些肢體動作,如果沒有先讀那本書,後面的動作則無法成立。
以上是一個簡單的 Rust 實作 Async 的方式,在依賴項的部分,你也可以使用更多人在用的 tokio 來實現非同步。