如果過去有學習過 Python 的 asyncio 的話,應該可以當時學到的是類似這樣:
那上面的 event loop 就很類似 rust 中的 runtime,比較不同的是 Python 受到 GIL 的限制,本質上還是單核在執行,但 Rust 的 thread 是可以切換 核心執行的.
那接下來先來說明 runtime 的使用
Tokio runtime 是一個 事件驅動 (event-driven) 的執行環境,專門用來跑 非同步任務(async fn)和處理 I/O。
它包含三個主要元件:
Tokio 提供兩種主要 runtime 型態:
Runtime 類型 | 特點 | 適用情境 |
---|---|---|
Multi-thread(預設) | 使用多個 OS thread(通常等於 CPU 核心數)做任務分配,可以同時跑多個 task | 高併發伺服器、需要充分利用多核 CPU |
Current-thread(單執行緒) | 所有任務在同一個 thread 上依序切換執行 | 嵌入式、限制不能多線程的環境、或 debug 用 |
但默認情況下,創建出來的runtime都是多線程runtime,且沒有指定工作線程數量時,默認的工作線程數量將和CPU核數(虛擬核,即CPU線程數)相同。
只有明確指定,才能創建出單一線程的runtime。
Tokio runtime 有三種建立方式:
#[tokio::main] // 預設 multi-thread
async fn main() {
println!("Hello Tokio!");
}
use tokio::runtime::Runtime;
fn main() {
let rt = Runtime::new().unwrap();
rt.block_on(async {
println!("Manual runtime");
});
}
use tokio::runtime::Builder;
fn main() {
let rt = Builder::new_multi_thread()
.worker_threads(4) // 自訂 thread 數
.enable_all() // 啟用 I/O, timer 等功能
.build()
.unwrap();
rt.block_on(async {
println!("Custom thread pool");
});
}
在 Tokio 中,Task 就是一個正在 runtime 中執行的 Future。
它的角色類似:
一個 Task 是由 runtime 接手管理的,會:
在 Tokio 中,通常用 tokio::spawn 建立 task。
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
// 建立一個 task(非阻塞)
let handle = tokio::spawn(async {
sleep(Duration::from_secs(1)).await;
println!("Task 完成!");
42 // return 值
});
println!("Main 執行中...");
let result = handle.await.unwrap(); // 等待 task 結束
println!("Task 回傳: {}", result);
}
重點
Tokio 會在 runtime 的 thread pool 中 並行 調度多個 task。
#[tokio::main]
async fn main() {
let t1 = tokio::spawn(async { "A" });
let t2 = tokio::spawn(async { "B" });
let (a, b) = tokio::join!(t1, t2); // 等待兩個 task 完成
println!("{:?} {:?}", a.unwrap(), b.unwrap());
}
這和 Python asyncio 的:
asyncio.gather(t1(), t2())
概念幾乎一樣。
明天會接著介紹 tokio task 的通信和同步.