题目
使用 Mutex 实现线程安全的计数器
信息
- 类型:问答
- 难度:⭐
考点
线程创建, 共享数据保护, Mutex 基础使用
快速回答
实现线程安全计数器的核心步骤:
- 使用
Arc<Mutex<T>>包装共享数据 - 通过
lock().unwrap()获取互斥锁 - 在锁作用域内修改数据
- 使用
thread::spawn创建线程
最终确保多个线程能安全地递增计数器。
解析
问题场景
在并发编程中,当多个线程需要修改同一份数据时,必须通过同步机制防止数据竞争。本题要求创建两个线程,各将共享计数器递增10次,最终结果应为20。
核心原理
Rust 通过所有权系统和类型系统保证并发安全:
- Mutex(互斥锁):确保同一时间只有一个线程能访问数据
- Arc(原子引用计数):允许在多线程间安全共享所有权
- 获取锁后返回
MutexGuard,实现:- 自动释放锁(Drop trait)
- 提供数据的可变引用
代码实现
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// 创建线程安全的计数器
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..2 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
for _ in 0..10 {
// 获取锁并修改数据
let mut num = counter.lock().unwrap();
*num += 1;
} // 锁在此自动释放
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// 验证结果
println!("Final count: {}", *counter.lock().unwrap());
}最佳实践
- 锁作用域最小化:在
{}内完成操作后立即释放锁 - 错误处理:生产代码中应处理
lock()的Result而非直接unwrap() - 避免死锁:不在持锁时调用可能阻塞的函数
常见错误
- 直接使用 Mutex 不加 Arc:
Mutex本身不实现Clone,无法跨线程共享 - 忘记获取锁:直接修改数据会导致编译错误(所有权检查)
- 长期持锁:在循环内持锁执行耗时操作会降低并发性能
扩展知识
- RwLock:读写分离锁,适用于读多写少场景
- 原子类型:
AtomicUsize等适用于简单操作,无需显式锁 - 死锁预防:按固定顺序获取多个锁,或使用
try_lock