【譯】Async/Await(五)—— Executors and Wakers


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

參考資料

[1]

線程池: 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

```


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM