可能导致死锁的场景
- 忘记通知:一个线程在等待条件变量,而另一个应该通知条件变量的线程永远不执行通知操作,导致等待线程永远阻塞。
- 锁顺序不一致:不同线程以不同顺序获取和释放锁,例如线程A获取锁
lock_a
,然后尝试获取锁lock_b
,而线程B先获取锁lock_b
,再尝试获取锁lock_a
,如果两个线程同时进行这些操作,就会发生死锁。
- 持有锁进行通知:在持有条件变量关联的互斥锁时进行通知,可能会导致等待线程获取锁失败,因为通知线程持有锁,等待线程无法获取锁从而继续等待,而通知线程又在等待等待线程完成某些操作后释放锁,形成死锁。
预防死锁的策略
- 确保通知:在适当的时机调用
notify_one
或notify_all
方法通知等待的线程。
- 固定锁顺序:在所有线程中以相同的顺序获取和释放锁。
- 释放锁后通知:在通知条件变量之前释放与之关联的互斥锁。
代码示例
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
// 等待线程
let handle = thread::spawn(move || {
let (lock, cvar) = &*pair;
let mut data = lock.lock().unwrap();
while!*data {
data = cvar.wait(data).unwrap();
}
println!("等待线程收到通知");
});
// 通知线程
thread::sleep(std::time::Duration::from_secs(1));
let (lock, cvar) = &*pair2;
{
let mut data = lock.lock().unwrap();
*data = true;
} // 离开作用域,释放锁
cvar.notify_one();
handle.join().unwrap();
}
代码解释
- 创建条件变量和互斥锁:通过
Arc
和Mutex
、Condvar
创建共享数据和同步工具。
- 等待线程:使用
while
循环和cvar.wait
等待条件变量通知,确保即使虚假唤醒也能正确等待。
- 通知线程:先获取锁,修改共享数据,然后离开作用域释放锁,最后调用
notify_one
通知等待线程。这样避免了在持有锁时通知导致的死锁问题。同时通过固定锁获取和释放顺序,以及确保通知操作,预防了死锁的发生。