ttf_openfont可以多次调用吗_【译文】Rust futures: async fn中的thread::sleep和阻塞调用...
URL: https://blog.hwc.io/posts/rust-futures-threadsleep-and-blocking-calls-inside-async-fn/
近來,關于Rust的futures和async/await如何工作(“blockers”,哈哈),我看到存在一些普遍的誤解。很多新用戶為async/await帶來的重大改進而感到興奮,但是卻被一些基本問題所困擾。即使有了async/await,并發依然很難。文檔還在進一步充實,阻塞/非阻塞之間的交互很棘手。希望本文對你有所幫助。
(本篇主要是關于特定的痛點;有關Rust中的異步編程的概述,請轉至本書)
TLDR(Too Long Didn't Read):小心在async fn中使用昂貴的阻塞調用!如果不確定, 鑒于Rust std庫中幾乎所有都是阻塞的,所以就要注意哪些調用是耗時的!
雖然我認為任何人都可能犯這個錯誤(在引入足夠的負載來顯著地阻塞線程之前,往往察覺不到),但是初學者尤為如此。下面的場景可能有點冗長,但我認為有必要展示一下在async fn中實現阻塞調用是多么容易。
不要用 std::thread::sleep sleep
在研究了一個簡單的示例之后,Rust異步新手可能要做的第一件事就是去驗證程序真正實現了異步。因此,我們使用Rust異步書籍中的示例:
use futures::join; ? async fn get_book_and_music() -> (Book, Music) {let book_fut = get_book();let music_fut = get_music();join!(book_fut, music_fut) }即使你在get_book和get_music內部打日志,也無法通過簡單的方式來判斷它們是同時運行的,因為任何一次運行都可能產生恰好與代碼順序匹配的輸出。你必須多次運行該程序,才能查看日志記錄順序是否可以翻轉(如果不翻轉怎么辦?)。
如果想看到get_book和get_music是100%同時運行,你可能會想到記錄它們的開始時間,并查看開始時間是否相同。但是,等等,如果開始時間仍然是串行的,但fn運行得如此之快,看起來仍然像是并發該怎么辦?
引入一個延遲!比如(清楚起見,使用偽碼):
async fn get_book() {println!("book start: time {}", current_time());std::thread::sleep(one_second);println!("book end: time {}", current_time()); }在get_book和get_music內部延遲1秒,我們希望,如果是并發的話,則會看到以下的輸出:
book start: time 0.00music start: time 0.00
book end: time 1.00
music end: time 1.00
如果是串行,我們預期是:
book start: time 0.00book end: time 1.00
music start: time 1.00
music end: time 2.00
你認為會發生什么, 串行或并發?
你已經讀了這篇文章的標題,可能會猜到get_book和get_music是按順序執行的。但為什么!?異步fn中的所有內容不是都應該同時運行嗎?
在繼續解釋之前,可以看個問題已經多次被問到:
reddit 1 reddit 2 reddit 3 stackoverflow 1
因此,如果你也犯了這個錯誤,不用擔心,其他許多人也有同樣的經歷。
什么搞錯了?為什么async不行?
我不會在這里深入討論futures和async/await(本書是一個很好的起點)。我只想指出造成困惑的兩個可能的根源:
std::thread::sleep 會阻塞?
對于新手來說,std::thread::sleep會造成阻塞可能并不是顯而易見的。盡管事后看起來很明顯,但是當嘗試掌握全新的程序執行范式時,卻很容易忽略。
即使你大致了解并發,也可能不知道thread::sleep是具體如何實現的。一些上層推理加上一些示例(例如上述)可能會幫助你理解。但是文檔中并沒有明說“此調用是阻塞的,你不應該在異步上下文中使用它”,并且非系統程序員可能不會過多地考慮“將當前線程置于睡眠狀態”。
(具有諷刺意味的是,如果人們的異步編程的心智模型是讓Future進入“睡眠”狀態從而得以讓其他工作發生,那么thread::sleep可能會特別令人困惑)。
async 可以做什么?
但是有些人可能會說:“如果thread::sleep阻塞了怎么辦?不是把它放在async fn中就好了嗎?”
為了理解那些在線討論,(就要知道)他們的想法是以為async可以使代碼塊或函數內部的所有內容異步。
首先,我想說這是有意義的;async/await存在的部分原因是它使每個人都容易進行異步操作。而且,如果你從較高的層次上理解了并發模型(事件循環,通常是嘗試不阻塞線程),那么可能沒有特定的理由導致async不能僅僅通過使事物定義為異步來起作用。那絕對是最簡單,最符合人體工程學的方式。
不幸的是,這不是Rust的async范式的工作方式。async功能很強大,但從本質上講,它只是提供了一種更好的處理Futures的方法。而且Future不只是自動將阻塞調用移到一邊以允許完成其他工作;它要結合使用具備輪詢和異步運行時這種完全獨立的系統,才能進行異步舞蹈。在該系統內進行的任何阻塞調用仍將處于阻塞狀態。
這可能會造成一些困惑,因為async/await允許我們編寫看起來更像常規(阻塞)代碼的代碼。那就是async/await的await部分進入的地方。當你在async塊中awaitfuture時,它能夠將自己安排在線程外并為其他任務讓路。阻塞代碼可能看起來很相似,但是由于它不是future,所以無法await,也無法為其他任務騰出空間。
因此,下面不會阻塞,但是await可以讓你編寫看起來與阻塞調用非常相似的代碼:
async {let f = get_file_async().await;let resp = fetch_api_async().await; }下面在每行調用時阻塞:
async {let f = get_file_blocking();let resp = fetch_api_blocking(); }下面將不能通過編譯:
async {let f = get_file_blocking().await;let resp = fetch_api_blocking().await; }在這里什么也沒有發生(您必須在async內部awaitfutures!):
async {let f = get_file_async();let resp = fetch_api_async(); }總的來說,最好將async視為允許在函數或塊中 await 的東西,但實際上并不會使任何東西異步。
如何不阻塞
如果想要異步fn取消阻塞該怎么辦?
你可以找到一個異步替代方案:當thread::sleep阻塞時,你可以使用它們(取決于你選擇的運行時生態系統):
- async_std::task::sleep (1.0)
- tokio::time::delay_for (0.2.0)
tokio和async_std都為其他阻塞操作(例如文件系統和tcp流訪問)提供了異步替代方法。
另一個選擇是將阻塞調用移到另一個線程。
- tokio::task::spawn_blocking (0.2.0)
- async_std::task::spawn_blocking (1.0)
這要求你的運行時具有專用于卸載阻塞調用的機制(例如線程池)。
我還提出了一些問題,試圖防止其他人陷入這個陷阱:
- async-book
- clippy
結語
希望該博客能夠闡明有關阻塞調用如何與Rust的并發模型進行交互的一些信息!隨時提供反饋給我。
總結
以上是生活随笔為你收集整理的ttf_openfont可以多次调用吗_【译文】Rust futures: async fn中的thread::sleep和阻塞调用...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redmi 官宣联名《哈利・波特》,打造
- 下一篇: 索引超出矩阵维度_搜索引擎技术之倒排索引