為什麼是非同步?

我們都愛 Rust 讓我們有辦法寫出快速又安全的軟體。但為什麼要編寫非同步程式碼呢?

非同步的程式碼可以在同個作業系統的執行緒下同時執行多項任務。在一個典型的多執行緒程式中,如果要在同一時間下載多個不同的網頁,你需要講工作分佈到兩個相異的執行緒中,如下:


# #![allow(unused_variables)]
#fn main() {
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");
}
#}

對大部分的應用程式來說,這還行,畢竟執行緒就是設計來做這種事:同時執行多項不同的任務。然而,一些限制接踵而來。在不同的執行緒與切換,以及在執行緒間資料共享的開銷非常多。就算是無所事事在一旁涼快的一個執行緒仍然消耗寶貴的系統資源。而非同步程式就算設計來消滅這些開銷。我們可以透過 Rust 的 async/.await 語法,重寫上面這個函數,這個語法標記允許我們在不建立多個執行緒的條件下同時執行多項任務:


# #![allow(unused_variables)]
#fn main() {
async fn get_two_sites_async() {
    // Create a two different "futures" which, when run to completion,
    // will asynchronously download the webpages.
    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.
    join!(future_one, future_two);
}
#}

總體來說,非同步應用程式有潛力比相對應的多執行緒實作來得更快更節省資源。不過,它仍然有個成本。執行緒由作業系統原生提供,使用上不需要特殊的程式設計模式,任意的函式都可建立執行緒,且呼叫使用執行緒的函式就和呼叫普通函式一樣容易。然而,非同步的函式需要程式語言或函式庫特殊的支援。在 Rust 的世界,async fn 會建立一個非同步函式,函式會返回一個 Future。若要執行函式主體,則返回的 Future 必須執行直到完成。

傳統多執行緒的程式可以達到高效,而 Rust 少量的記憶體足跡與預測性代表了用了 async,你可以走得更遠。非同步程式設計模型帶來的複雜度並非總是值得,該仔細考慮你的應用程式是否在簡單多執行緒模型下能跑得更好。