阻塞執行器

大多數非同步執行環境只允許並行執行 IO 工作。這表示阻塞 CPU 的工作會阻塞執行器,不讓其他工作執行。一個簡單的解決方法,就是盡可能使用與非同步機制同等的方法。

use futures::future::join_all;
use std::time::Instant;

async fn sleep_ms(start: &Instant, id: u64, duration_ms: u64) {
    std::thread::sleep(std::time::Duration::from_millis(duration_ms));
    println!(
        "future {id} slept for {duration_ms}ms, finished after {}ms",
        start.elapsed().as_millis()
    );
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let start = Instant::now();
    let sleep_futures = (1..=10).map(|t| sleep_ms(&start, t, t * 10));
    join_all(sleep_futures).await;
}
  • 執行程式碼,然後您會發現休眠狀態是連續發生,而非並行發生。

  • "current_thread" 變種版本會將所有工作放在單一執行緒。這樣的效果更加明顯,但多執行緒變種版本仍存在該錯誤。

  • std::thread::sleep 改為 tokio::time::sleep,並等待結果。

  • 另一個修正方式為 tokio::task::spawn_blocking,這可產生實際執行緒,並將控制代碼轉換為 Future,而不會阻塞執行器。

  • 請勿將工作視為 OS 執行緒。工作不會 1 對 1 對應,且大部分執行器會允許在單一 OS 執行緒中執行多項工作。在透過 FFI 與其他程式庫互動時,這尤其會造成問題,因為程式庫可能會依附執行緒本機儲存空間,或對應至特定 OS 執行緒 (例如 CUDA)。在這種情況下,請優先使用 tokio::task::spawn_blocking

  • 請謹慎使用同步互斥鎖。將互斥鎖保留在 .await 上,可能會導致其他工作造成阻塞,且該工作可能在同一執行緒上執行。