原文標題:Async/Await
原文連結:https://os.phil-opp.com/async-await/#multitasking
公眾號: Rust 碎碎念
翻譯 by: Praying
Executors and Wakers
使用 async/await,可以讓我們以一種全完非同步的方式來與 future 進行更為自然地協作。然而,正如我們之前所瞭解到的,future 在被輪詢之前什麼事也不會做。這意味著我們必須在某個時間點上呼叫poll
,否則非同步的程式碼永遠都不會執行。
對於單個的 future,我們總是通過使用一個迴圈手動地等待每個 future。但這種方式十分地低效,且對於一個建立大量 future 的程式來講也不適用。針對這個問題的最常見的解決方式是定義一個全域性的executor
Executors(執行器)
executor 的作用在於能夠產生 future 作為獨立的任務,通常是通過某種spawn
方法。接著 executor 負責輪詢所有的 future 直到它們完成。集中管理所有的 future 的巨大優勢在於,只要當一個 future 返回Poll::Pending
時,executor 就可以切換到另一個 future。因此,非同步操作可以並行執行並且 CPU 始終儲存繁忙。
許多 executor 的實現充分利用 CPU 多核心的優勢,它們建立了一個執行緒池[1],該執行緒池能夠在工作足夠多的情況下充分利用所有的核心,並且使用類似work stealing[2]的方式在核心之間進行負載均衡。還有針對嵌入式系統優化了低延遲和記憶體負載的特殊的 executor 實現。
為了避免重複輪詢 future 的負擔,executor 通常會充分利用由 Rust 的 future 支援的 waker API。
Waker
Waker API 背後的設計理念是,一個特定的Waker[3]型別,包裝在Context
型別中,被傳遞到poll
的每一次執行。這個Waker型別
由 executor 建立,並且可以被非同步任務用來通知自己的完成。因此,executor 不需要在一個 future 返回Poll::Pending
之前對其呼叫poll
,直到它被對應的 waker 呼叫。
這可以通過一個小例子來闡述:
async fn write_file() {
async_write_file("foo.txt", "Hello").await;
}
這個函式非同步地把一個字串“Hello”寫入到檔案foo.txt
中。因為硬碟寫入需要一點兒時間,所以 future 上的第一次poll
呼叫很大可能返回Poll::Pending
。儘管如此,硬碟驅動將把傳遞給poll
呼叫的Waker
儲存起來,並在當檔案被完全寫入磁碟後使用它來提醒 executor,通過這種方式,executor 在收到 waker 提醒之前不需要浪費時間一次又一次地去輪詢這個 future。
當我們在後面的章節實現自己的支援 waker 的 executor 時,我們就會看到Waker
型別更詳細的工作原理。
協作式多工?
在本文(系列)開頭,我們討論了搶佔式和協作式多工。搶佔式多工依賴於作業系統在執行中的任務間進行強制切換,協作式多工則需要任務通過一個yield
操作自願放棄對 CPU 的控制權。協作式多工的巨大優勢在於,任務可以自己儲存自身狀態,從而產生更為高效的上下文切換,並使得在任務間共享相同的呼叫棧成為可能。
雖然看上去可能不太明顯,但是 future 和 async/await 是一種協作式多工模式的實現:
每個被新增到 executor 的 future 是一個協作式任務。
不同於顯式的
yield
操作,future 通過返回Poll::Pending
(或者是結束時的Poll::Ready
)來放棄對 CPU 核心的控制權。沒有什麼可以強制讓 future 放棄 CPU,future 可以永遠不從
poll
裡面返回,例如,無限迴圈。因為每個 future 都能阻塞 executor 中其他 future 的執行,所以我們需要確信它們不是惡意的。
Futures 內部儲存了需要在下次
poll
呼叫繼續執行所需的所有狀態。通過 async/await,編譯器會自動探測所有需要的變數並將其儲存在生成的狀態機內部。只儲存繼續執行需要的最小狀態 因為 poll
方法在返回時放棄了呼叫棧,所以同一個棧可以被用於輪詢其他的 future。
我們可以看到,future 和 async/await 完美契合協作式多工模式,它們只是用了一些不同的技術。接下來,我們將會交替使用“任務(task)”和“future”。
參考資料
執行緒池: https://en.wikipedia.org/wiki/Thread_pool
[2]work stealing: https://en.wikipedia.org/wiki/Work_stealing
[3]Waker: https://doc.rust-lang.org/nightly/core/task/struct.Waker.html