Day 23: 智能指针:Arc与Mutex
昨天我们学习了Rc和RefCell,它们解决了单线程环境下的共享所有权和内部可变性。但Rc是非线程安全的——它在多线程场景下可能导致数据竞争和内存不安全。
Rust的类型系统将线程安全也纳入了类型检查。今天我们将学习两个用于多线程的智能指针:Arc(Atomic Reference Counting)和Mutex(Mutual Exclusion)。
2. Arc:原子引用计数
Section titled “2. Arc:原子引用计数”Arc是Rc的线程安全版本。“原子”意味着引用计数的增减操作是原子的(不可中断的),能够在多线程环境下正确工作。
use std::sync::Arc;use std::thread;
fn main() { let data = Arc::new(5);
// 使用Arc::clone()克隆 let data2 = Arc::clone(&data);
// 在新线程中使用 let handle = thread::spawn(move || { println!("线程中的数据: {}", data2); });
// 主线程也可以访问 println!("主线程中的数据: {}", data);
handle.join().unwrap();}Arc::clone()同样不会深拷贝数据,只是增加引用计数。Arc的额外开销来自于原子操作的实现,因此如果没有多线程共享需求,应该使用Rc。
3. Arc 在多线程间的共享
Section titled “3. Arc 在多线程间的共享”use std::sync::Arc;use std::thread;
fn main() { let data = Arc::new(vec![1, 2, 3]);
let mut handles = vec![];
// 创建多个线程,每个线程都引用同一个Arc for i in 0..3 { let data_clone = Arc::clone(&data); let handle = thread::spawn(move || { println!("线程 {} 看到的数据: {:?}", i, data_clone); }); handles.push(handle); }
for handle in handles { handle.join().unwrap(); }
println!("最终数据: {:?}", data);}每个线程都获得了数据的独立引用,Arc保证所有线程看到一致的引用计数和数据。
4. Mutex:互斥锁
Section titled “4. Mutex:互斥锁”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_clone = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter_clone.lock().unwrap(); *num += 1; println!("计数: {}", *num); }); handles.push(handle); }
for handle in handles { handle.join().unwrap(); }
println!("最终计数: {}", *counter.lock().unwrap());}lock()方法返回MutexGuard,这是一个 RAII 风格的锁。当MutexGuard离开作用域时,锁自动释放,即使发生panic也不会死锁。
5. Arc 和 Mutex 组合:多线程共享可变状态
Section titled “5. Arc 和 Mutex 组合:多线程共享可变状态”这是Rust多线程编程中最常用的模式之一:
use std::sync::{Arc, Mutex};use std::thread;use std::time::Duration;
struct BankAccount { balance: f64,}
fn main() { let account = Arc::new(Mutex::new(BankAccount { balance: 1000.0 }));
let mut handles = vec![];
// 存款线程 let deposit_handle = { let account = Arc::clone(&account); thread::spawn(move || { let mut bal = account.lock().unwrap(); *bal.balance += 500.0; println!("存款后余额: {}", bal.balance); }) }; handles.push(deposit_handle);
// 取款线程 let withdraw_handle = { let account = Arc::clone(&account); thread::spawn(move || { thread::sleep(Duration::from_millis(10)); // 确保后执行 let mut bal = account.lock().unwrap(); *bal.balance -= 200.0; println!("取款后余额: {}", bal.balance); }) }; handles.push(withdraw_handle);
for handle in handles { handle.join().unwrap(); }
println!("最终余额: {}", *account.lock().unwrap().balance);}6. Mutex的其他特性
Section titled “6. Mutex的其他特性”死锁避免:Mutex不会自动检测死锁,开发者需要自行避免。常见的死锁原因是一个线程持有锁的同时等待另一个线程释放锁。
use std::sync::{Arc, Mutex};use std::thread;
// 错误示例:可能导致死锁fn deadlock_example() { let mutex1 = Arc::new(Mutex::new(1)); let mutex2 = Arc::new(Mutex::new(2));
let mutex1_clone = Arc::clone(&mutex1); let mutex2_clone = Arc::clone(&mutex2);
let handle1 = thread::spawn(move || { let _lock1 = mutex1_clone.lock().unwrap(); println!("线程1持有mutex1"); let _lock2 = mutex2_clone.lock().unwrap(); // 可能永久等待 });
let handle2 = thread::spawn(move || { let _lock2 = mutex2_clone.lock().unwrap(); println!("线程2持有mutex2"); let _lock1 = mutex1_clone.lock().unwrap(); // 可能永久等待 });
// 使用try_lock()替代lock()可以避免永久阻塞}Poisoning:如果持有锁的线程panic了,Mutex会被”毒害”(poisoned)。后续lock()会返回PoisonError,防止数据处于不一致状态。
use std::sync::Mutex;
fn main() { let mutex = Mutex::new(5);
// 手动模拟panic场景 let result = std::panic::catch_unwind(|| { let _guard = mutex.lock().unwrap(); panic!("假装panic"); });
match result { Ok(_) => println!("没有panic"), Err(_) => println!("Mutex被毒害了"), }}7. RwLock:读写锁
Section titled “7. RwLock:读写锁”对于读多写少的场景,Mutex的效率不高。RwLock允许多个读线程或单个写线程并发访问。
use std::sync::{Arc, RwLock};use std::thread;
fn main() { let data = Arc::new(RwLock::new(0)); let mut handles = vec![];
// 读线程 for _ in 0..3 { let data_clone = Arc::clone(&data); handles.push(thread::spawn(move || { let value = data_clone.read().unwrap(); println!("读取值: {}", *value); })); }
// 写线程 let write_clone = Arc::clone(&data); handles.push(thread::spawn(move || { let mut value = write_clone.write().unwrap(); *value += 1; println!("写入新值: {}", *value); }));
for handle in handles { handle.join().unwrap(); }}今天我们学习了Rust中用于多线程编程的两种关键智能指针:
-
Arc:原子引用计数指针,用于多线程间共享所有权。它是Rc的线程安全版本。
-
Mutex:互斥锁,通过独占访问保证线程安全的数据修改。配合Arc使用,是Rust多线程安全编程的基础。
-
RwLock:读写锁,提供了比Mutex更好的读并发性能。
Rust的所有权系统与线程安全紧密结合,编译器会在编译期就阻止大部分数据竞争问题,这使得Rust成为编写安全并发代码的理想选择。