1. 為什麼需要 Arc
昨天學到的 Rc 可以讓多個擁有者共享同一份資料,但它不能跨執行緒使用,在多執行緒環境中,如果多個 thread 同時修改引用計數,會導致資料競爭(data race)。為了解決這個問題,Rust 提供了 Arc(Atomic Reference Counted),它和 Rc 類似,但內部使用原子操作(atomic operation)來確保執行緒安全,因此可以安全地在多執行緒間共享資料。
2. Arc 的基本用法
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new(String::from("Rust concurrency"));
let mut handles = vec![];
for i in 0..3 {
let shared_data = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("Thread {} says: {}", i, shared_data);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
輸出:
Thread 0 says: Rust concurrency
Thread 2 says: Rust concurrency
Thread 1 says: Rust concurrency
這裡每個執行緒都透過 Arc::clone(&data) 取得同一份資料的共享擁有權,不會發生記憶體安全問題,Arc 會在最後一個執行緒結束時自動釋放記憶體。
3. 結合 Mutex 讓 Arc 內部資料可變
由於 Arc 只提供共享不可變資料,如果需要修改內容,就要搭配 Mutex 使用:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
輸出:
Result: 5
這裡 Arc 讓多執行緒能共享 counter,而 Mutex 則確保同一時間只有一個執行緒能修改它,避免 data race。
4. Arc 的釋放機制
與 Rc 一樣,Arc 透過引用計數追蹤所有擁有者,當最後一個 Arc 實例離開作用域時,記憶體會被釋放。
不同的是,Arc 的計數操作是原子性的,所以在多執行緒下也不會出現競爭條件。
5. 學習心得與補充
今天學到的 Arc 讓我更完整地理解 Rust 在多執行緒下的安全策略。Rust 把安全機制內建在型別系統裡,用 Arc 和 Mutex 就能在保證安全的同時寫出簡潔的程式。雖然一開始要理解為什麼 Rc 不能跨執行緒、為什麼要加 Mutex 這些概念有點多,但實際動手寫之後就能感受到它的邏輯非常清楚。我覺得今天的學習讓我開始能放心地寫多執行緒程式,而不再害怕那些難抓的記憶體錯誤。