iT邦幫忙

2025 iThome 鐵人賽

DAY 10
1

Tokio

如果過去有學習過 Python 的 asyncio 的話,應該可以當時學到的是類似這樣:

  • 當我執行一個程式時,作業系統會建立一個 process(行程)做為資源邊界;CPU 真正排程的是 process 內的 threads
  • multiprocessing 指建立多個 process,彼此記憶體隔離;每個 process 內可以有 1 個或多個 threads。OS 可能將這些 threads 分配到多個 CPU 核心上並行運作。
  • multithreading 指在同一個 process 內開多個 threads,共享記憶體、切換成本低,但要小心同步。
  • coroutine(async/await) 是在少量 threads 上實現大量非阻塞並發的方式;你用 await 定義等待點,由event loop 在等待時切換到其他協程,整體提升併發效率(I/O 密集最顯著)。

那上面的 event loop 就很類似 rust 中的 runtime,比較不同的是 Python 受到 GIL 的限制,本質上還是單核在執行,但 Rust 的 thread 是可以切換 核心執行的.

那接下來先來說明 runtime 的使用


Tokio runtime 是一個 事件驅動 (event-driven) 的執行環境,專門用來跑 非同步任務(async fn)和處理 I/O。

它包含三個主要元件:

  1. Executor(任務執行器)
    • 負責調度並執行 Future(非同步任務)
  2. Reactor(事件反應器)
    • 監聽 OS 的事件(例如 socket 收到資料)
  3. Timer(計時器管理)
    • 負責非阻塞的 sleep、timeout 等功能

Tokio 提供兩種主要 runtime 型態:

Runtime 類型 特點 適用情境
Multi-thread(預設) 使用多個 OS thread(通常等於 CPU 核心數)做任務分配,可以同時跑多個 task 高併發伺服器、需要充分利用多核 CPU
Current-thread(單執行緒) 所有任務在同一個 thread 上依序切換執行 嵌入式、限制不能多線程的環境、或 debug 用

但默認情況下,創建出來的runtime都是多線程runtime,且沒有指定工作線程數量時,默認的工作線程數量將和CPU核數(虛擬核,即CPU線程數)相同。

只有明確指定,才能創建出單一線程的runtime。


建立 runtime 的方式

Tokio runtime 有三種建立方式:

(1) 自動建立 —

#[tokio::main]

#[tokio::main] // 預設 multi-thread
async fn main() {
    println!("Hello Tokio!");
}

(2) 手動建立 —

Runtime::new()

use tokio::runtime::Runtime;

fn main() {
    let rt = Runtime::new().unwrap();
    rt.block_on(async {
        println!("Manual runtime");
    });
}

(3) 自訂 thread 數 —

Builder

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");
    });
}

Task

在 Tokio 中,Task 就是一個正在 runtime 中執行的 Future

它的角色類似:

  • Python asyncio 的 asyncio.Task
  • 作業系統 中的「可被調度的工作單位」

一個 Task 是由 runtime 接手管理的,會:

  1. 放到 執行隊列(由 executor 管理)
  2. 等待被分配到某個 thread 執行
  3. 執行到 I/O 等待點(await)時讓出控制權
  4. 事件完成後,runtime 再把它喚醒繼續跑

2. Task 的建立

在 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::spawn 會把你的 async block 包成一個 task 丟給 runtime
  • handle.await 才會等待它完成(類似 Python await asyncio.gather)
  • 如果不 .await,task 會在背景執行

3. 多個 Task 的並行執行

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 的通信和同步.


上一篇
【Day9】- Rust(Enum, Generics)
下一篇
【Day11】- Rust(Tokio)
系列文
NautilusTrader 架構解析:Rust 在高效能量化交易平台中的角色與優勢22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言