本文 AI 產出,尚未審核

Rust 並行與多執行緒 ── 執行緒(Threads)基礎

簡介

在現代硬體上,CPU 常常擁有多顆核心(core),只有善用 多執行緒,才能真正發揮硬體的運算效能。
對於 系統程式伺服器、或是 資料處理 等需要同時處理多項工作、降低延遲的應用而言,掌握執行緒的使用是必備技能。

Rust 以 所有權(ownership)借用檢查(borrow checker) 為核心,提供了安全且高效的執行緒模型。即使是初學者,也能在不犧牲安全性的前提下,寫出可靠的並行程式。

本文將從 建立執行緒資料共享同步機制 等基礎概念出發,搭配實用範例,說明在 Rust 中如何正確且有效地使用執行緒。


核心概念

1. std::thread 模組與 thread::spawn

Rust 標準函式庫的 std::thread 提供了最直接的執行緒 API。
thread::spawn 會接受一個 閉包(closure),在新執行緒中執行,並回傳 JoinHandle<T>,其中 T 為閉包的返回值類型。

use std::thread;

fn main() {
    // 建立一個執行緒,印出訊息後自動結束
    let handle = thread::spawn(|| {
        println!("Hello from a new thread!");
    });

    // 主執行緒繼續執行
    println!("Hello from the main thread!");

    // 等待子執行緒結束
    handle.join().unwrap();
}

重點join 必須被呼叫,否則子執行緒可能在主執行緒結束前被強制終止,導致資源未正確釋放。


2. 傳遞資料給執行緒

由於 Rust 的所有權規則,閉包會 捕獲它所使用的變數。
如果要把資料移交給執行緒,必須使用 move 關鍵字,將所有權搬移(move)到子執行緒。

use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5];

    // 使用 move 把 data 的所有權搬移到新執行緒
    let handle = thread::spawn(move || {
        // 此處 data 已經是子執行緒的所有者
        let sum: i32 = data.iter().sum();
        println!("Sum in thread: {}", sum);
    });

    // 主執行緒此時已無法再使用 data
    // println!("{:?}", data); // 編譯錯誤

    handle.join().unwrap();
}

如果需要在多個執行緒之間共享同一筆資料,則必須使用 同步原語(例如 ArcMutex)來保證資料安全。


3. Arc(Atomic Reference Counted)與 Mutex

  • Arc<T>:多執行緒環境下的引用計數指標,允許多個執行緒共享同一筆資料的所有權。
  • Mutex<T>:互斥鎖,確保同一時間只有一個執行緒能夠存取被保護的資料。

下面的範例示範如何使用 Arc<Mutex<T>> 讓多個執行緒安全地累加計數器:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // 建立一個被 Mutex 包住的計數器,並用 Arc 共享所有權
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        // 為每個執行緒克隆一個 Arc(增加引用計數)
        let counter_clone = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            // 取得鎖,執行臨界區操作
            let mut num = counter_clone.lock().unwrap();
            *num += 1;
            // 鎖在作用域結束時自動釋放
        });
        handles.push(handle);
    }

    // 等所有執行緒結束
    for h in handles {
        h.join().unwrap();
    }

    println!("Final counter: {}", *counter.lock().unwrap());
}

技巧Arc::clone(&counter) 只會複製指標,成本極低;真正的資料仍只有一份。


4. channel(訊道)進行執行緒間通訊

有時候不需要共享可變資料,而是希望執行緒之間以 訊息傳遞 的方式協調工作。
Rust 提供了 std::sync::mpsc(multiple producer, single consumer)通道。

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    // 建立一個傳送端 (tx) 與接收端 (rx)
    let (tx, rx) = mpsc::channel();

    // 產生多個生產者執行緒
    for i in 0..5 {
        let tx_clone = tx.clone(); // 每個執行緒都有自己的傳送端
        thread::spawn(move || {
            thread::sleep(Duration::from_millis(50 * i));
            tx_clone.send(format!("Message from thread {}", i)).unwrap();
        });
    }

    // 主執行緒作為唯一的消費者
    for _ in 0..5 {
        let msg = rx.recv().unwrap(); // 阻塞等待訊息
        println!("Received: {}", msg);
    }
}

此範例展示 多生產者單消費者 的模式,適合「工作者池」或「事件驅動」的情境。


5. thread::sleepJoinHandle::join

  • thread::sleep:讓目前執行緒暫停指定時間,常用於模擬 I/O 或測試併發行為。
  • JoinHandle::join:等待執行緒結束並取得其返回值。若執行緒 panic,join 會回傳 Err.
use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        thread::sleep(Duration::from_secs(2));
        42 // 執行緒的返回值
    });

    println!("Waiting for the thread...");
    match handle.join() {
        Ok(v) => println!("Thread finished with value {}", v),
        Err(e) => eprintln!("Thread panicked: {:?}", e),
    }
}

常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記 move 閉包捕獲的變數仍屬於主執行緒,會導致編譯錯誤或資料競爭。 thread::spawn 時使用 move,明確搬移所有權。
資料競爭(Data Race) 多執行緒同時寫同一變數,未使用同步原語。 使用 Arc<Mutex<T>>RwLock<T> 包裝共享資料。
死鎖(Deadlock) 多把鎖以不同順序取得,導致相互等待。 統一鎖的取得順序,或使用 try_lock 失敗時回退。
過度產生執行緒 每個任務都建立新執行緒,系統資源耗盡。 使用 執行緒池(如 rayontokio)或 工作者模型
忘記 join 主執行緒提前結束,子執行緒被強制終止。 在需要時 呼叫 handle.join(),或使用 scopecrossbeam)確保生命週期。

最佳實踐

  1. 盡量使用不可變共享:如果資料不需要變更,使用 Arc<T>(無 Mutex)即可,避免不必要的鎖開銷。
  2. 限制鎖的範圍:只在必要的程式區段持有鎖,減少阻塞時間。
  3. 錯誤處理joinlocksendrecv 都可能失敗,務必使用 Result 處理。
  4. 測試與偵錯:利用 cargo test -- --nocapture 觀察執行緒輸出,或使用 RUST_BACKTRACE=1 追蹤 panic。
  5. 考慮使用高階抽象:對於複雜的併發需求,rayon(資料平行)或 tokio(非阻塞 async)能提供更安全、更高效的實作。

實際應用場景

場景 為何需要執行緒 典型實作方式
Web 伺服器 同時處理多個請求、避免阻塞 I/O 使用 tokio 的非阻塞執行緒或 rayon 處理 CPU 密集工作
資料批次處理 大量檔案或資料分割後平行計算 Arc<Mutex<Vec<T>>> + 多執行緒累加結果,或 rayon::par_iter
即時遊戲伺服器 網路訊息、物理模擬、AI 同時運算 工作者池(thread pool)分配不同任務,使用訊道傳遞結果
硬體驅動 / 嵌入式 中斷處理與背景任務分離 std::thread::spawn 搭配 Mutex/Condvar 進行資源同步
日誌與監控 高頻率寫入檔案或遠端服務,避免阻塞主流程 背景執行緒使用 channel 收集訊息,批次寫入

總結

  • 執行緒是 Rust 並行程式設計的基礎,透過 thread::spawnArcMutexchannel 等工具,我們可以在不犧牲安全性的前提下,寫出高效能的多執行緒程式。
  • 理解 所有權搬移(move)資料共享 以及 同步機制,是避免資料競爭與死鎖的關鍵。
  • 在實務開發中,適度使用執行緒池或高階抽象(如 rayontokio)可以降低程式碼複雜度,同時提升可維護性與效能。

掌握了本文的概念與範例,你就能在 Rust 中自信地運用執行緒,為各種 CPU 密集I/O 密集即時 的應用提供穩定且高效的併發解決方案。祝你寫程式順利,玩得開心! 🚀