面试题答案
一键面试避免死锁的方法
在Rust多线程程序中,当涉及多个Mutex
且不同线程可能以不同顺序获取锁时,为避免死锁,可以采用以下方法:
- 固定锁获取顺序:所有线程都按照相同的顺序获取锁。这样可以避免形成循环等待的情况,因为如果所有线程都以相同顺序获取锁,就不会出现一个线程持有锁A等待锁B,而另一个线程持有锁B等待锁A的情况。
- 使用
std::sync::TryLockError
进行尝试获取锁:通过使用try_lock
方法,线程可以尝试获取锁,如果获取失败,可以释放已获取的锁并重新尝试,或者采取其他措施,而不是一直阻塞等待,从而避免死锁。
分析代码潜在问题
上述代码存在死锁风险。主线程和新创建的线程获取锁的顺序不同。主线程先获取mutex2
,然后获取mutex1
;而新线程先获取mutex1
,然后获取mutex2
。如果主线程获取mutex2
后,新线程获取mutex1
,两个线程将互相等待对方释放锁,从而导致死锁。
改进方案
- 固定锁获取顺序:让两个线程都以相同顺序获取锁,例如都先获取
mutex1
,再获取mutex2
。
use std::sync::{Arc, Mutex};
fn main() {
let mutex1 = Arc::new(Mutex::new(0));
let mutex2 = Arc::new(Mutex::new(1));
let mutex1_clone = mutex1.clone();
let mutex2_clone = mutex2.clone();
std::thread::spawn(move || {
let _lock1 = mutex1_clone.lock().unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
let _lock2 = mutex2_clone.lock().unwrap();
});
let _lock1 = mutex1.lock().unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
let _lock2 = mutex2.lock().unwrap();
}
- 使用
try_lock
尝试获取锁:主线程和新线程使用try_lock
尝试获取锁,如果获取失败,释放已获取的锁并重新尝试。
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
let mutex1 = Arc::new(Mutex::new(0));
let mutex2 = Arc::new(Mutex::new(1));
let mutex1_clone = mutex1.clone();
let mutex2_clone = mutex2.clone();
thread::spawn(move || {
loop {
match (mutex1_clone.try_lock(), mutex2_clone.try_lock()) {
(Ok(lock1), Ok(lock2)) => {
drop(lock1);
drop(lock2);
break;
}
_ => {
thread::sleep(Duration::from_millis(100));
}
}
}
});
loop {
match (mutex1.try_lock(), mutex2.try_lock()) {
(Ok(lock1), Ok(lock2)) => {
drop(lock1);
drop(lock2);
break;
}
_ => {
thread::sleep(Duration::from_millis(100));
}
}
}
}