Skip to content

建立 2026-09-19 更新 2026-09-19

並行

並行(concurrency)程式讓多件事同時進行,但也最容易出現難以除錯的資料競爭(data race)。Rust 的所有權與型別系統能在編譯時擋下這類錯誤,這被稱為「無畏並行」(fearless concurrency)。本頁介紹執行緒、通道、共享狀態,並簡述 async。

執行緒

thread::spawn 建立新執行緒,回傳的 JoinHandle 可以呼叫 join() 等它結束。

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..=3 {
            println!("子執行緒:{i}");
        }
    });

    println!("主執行緒繼續執行");
    handle.join().unwrap(); // 等子執行緒完成
}

輸出的順序不固定,取決於作業系統排程。

用 move 把資料交給執行緒

新執行緒可能比建立它的函式活得更久,因此不能借用區域變數,必須用 move 取走所有權:

use std::thread;

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

    let handle = thread::spawn(move || {
        println!("子執行緒拿到:{:?}", numbers);
    });

    // println!("{:?}", numbers); // 編譯錯誤:已經被移進執行緒
    handle.join().unwrap();
}

通道:用傳訊息溝通

通道(channel)讓執行緒之間傳遞訊息,而不是共享記憶體。mpsc 代表「多個生產者、單一消費者」(multiple producer, single consumer):

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    for id in 1..=3 {
        let tx = tx.clone(); // 每個生產者各有一個發送端
        thread::spawn(move || {
            tx.send(format!("工人 {id} 完成")).unwrap();
        });
    }
    drop(tx); // 關閉原本的發送端,接收端才知道何時結束

    let mut results: Vec<String> = rx.iter().collect();
    results.sort();
    for r in results {
        println!("{r}");
    }
}

send移動訊息的所有權,所以送出後發送方就不能再使用它,避免兩邊同時修改。

共享狀態:Arc 與 Mutex

多個執行緒需要讀寫同一份資料時,使用:

  • Mutex<T>:互斥鎖,同一時間只有一個執行緒能取得資料。
  • Arc<T>:原子參照計數(atomic reference counting),是可跨執行緒使用的 Rc<T>
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        })); // num 離開作用域時自動解鎖
    }

    for h in handles {
        h.join().unwrap();
    }

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

lock() 回傳的守衛(guard)離開作用域時會自動解鎖,不用手動 unlock,也不會忘記。

為什麼編譯器能擋下資料競爭?

Rust 用兩個標記 trait 描述型別能否跨執行緒使用:

Trait 意義
Send 這個型別的所有權可以轉移到另一個執行緒
Sync 這個型別可以被多個執行緒同時以參照存取

Rc<T> 沒有實作 Send,所以把它丟進 thread::spawn 會編譯失敗,必須改用 Arc<T>。你不需要手動處理這些 trait,編譯器會自動推導並檢查。

async 簡介

上面的執行緒模型適合運算密集的工作。若程式要同時處理大量網路連線這類等待多於運算的 I/O,開一堆執行緒成本很高,此時會用 asyncawait

async fn fetch_data() -> String {
    String::from("資料")
}

要注意兩件事:

  • async fn 回傳的是 Future,本身不會執行,必須有人去「驅動」它。
  • Rust 標準函式庫不含執行器(runtime),需要引入 Tokio 之類的套件才能實際執行 async 程式碼。

初學階段不必急著學 async,先把執行緒、所有權與錯誤處理學好,再依需求(例如寫網路服務)學習。

該選哪一種?

情況 做法
運算密集的獨立任務 thread::spawn
執行緒間傳遞資料 通道 mpsc
多個執行緒共同讀寫同一份資料 Arc<Mutex<T>>
大量網路 I/O asyncawait 加上 Tokio 等 runtime

推薦影音

建立執行緒

簡述:Let's Get Rusty 依照官方書籍第 16 章製作,介紹 thread::spawnjoinmove 閉包。

訊息傳遞

簡述:同一系列的通道單元,介紹 mpsc 的發送端與接收端,對應本頁「通道」一節。

共享狀態

簡述:同一系列的共享狀態單元,介紹 MutexArc,對應本頁「共享狀態」一節。

async 入門

簡述:Let's Get Rusty 用約 20 分鐘說明 Rust 的 async 如何運作,適合在學完執行緒後,想了解 asyncawait 的人。

想繼續深入,請看 學習資源影音教學總覽