原文標題: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