面试题答案
一键面试可能导致死锁的场景
假设有两个线程 thread1
和 thread2
,以及两个共享资源 resource1
和 resource2
。
thread1
先获取 resource1
的锁,然后尝试获取 resource2
的锁;而 thread2
先获取 resource2
的锁,然后尝试获取 resource1
的锁。如果两个线程同时执行到获取第二个锁的步骤,就会发生死锁。
以下是示例代码:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let resource1 = Arc::new(Mutex::new(0));
let resource2 = Arc::new(Mutex::new(0));
let resource1_clone = resource1.clone();
let resource2_clone = resource2.clone();
let handle1 = thread::spawn(move || {
let _lock1 = resource1_clone.lock().unwrap();
thread::sleep(std::time::Duration::from_secs(1));
let _lock2 = resource2_clone.lock().unwrap();
});
let handle2 = thread::spawn(move || {
let _lock2 = resource2.lock().unwrap();
thread::sleep(std::time::Duration::from_secs(1));
let _lock1 = resource1.lock().unwrap();
});
handle1.join().unwrap();
handle2.join().unwrap();
}
通过资源分配图算法预防和检测死锁
- 资源分配图算法原理:资源分配图是一种描述系统中进程和资源之间关系的有向图。死锁检测算法通过对资源分配图进行化简,如果最终图中所有节点都能被化简掉,则系统没有死锁;否则,存在死锁。
- 在Rust中应用:在实际实现中,可以使用数据结构来表示资源分配图,例如使用
HashMap
来记录进程对资源的请求和分配关系。然后编写算法遍历这个图进行化简操作。
通过Rust特定编程模式预防和检测死锁
- 使用
std::sync::Mutex
的try_lock
方法:try_lock
方法尝试获取锁,如果锁不可用,它会立即返回Err
,而不是阻塞等待。通过这种方式,线程可以在获取锁失败时采取其他策略,避免死锁。- 修改后的代码如下:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let resource1 = Arc::new(Mutex::new(0));
let resource2 = Arc::new(Mutex::new(0));
let resource1_clone = resource1.clone();
let resource2_clone = resource2.clone();
let handle1 = thread::spawn(move || {
match resource1_clone.try_lock() {
Ok(_lock1) => {
thread::sleep(std::time::Duration::from_secs(1));
match resource2_clone.try_lock() {
Ok(_lock2) => {}
Err(_) => {
// 处理获取锁失败的情况
println!("Thread1 failed to acquire resource2");
}
}
}
Err(_) => {
println!("Thread1 failed to acquire resource1");
}
}
});
let handle2 = thread::spawn(move || {
match resource2.lock() {
Ok(_lock2) => {
thread::sleep(std::time::Duration::from_secs(1));
match resource1.lock() {
Ok(_lock1) => {}
Err(_) => {
println!("Thread2 failed to acquire resource1");
}
}
}
Err(_) => {
println!("Thread2 failed to acquire resource2");
}
}
});
handle1.join().unwrap();
handle2.join().unwrap();
}
- 使用
std::sync::Condvar
进行条件等待:Condvar
可以让线程在满足特定条件时才获取锁,避免不必要的锁竞争,从而预防死锁。例如,在多个线程访问共享资源时,只有当资源满足某个条件时才允许访问。- 示例代码:
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
fn main() {
let resource = Arc::new((Mutex::new(0), Condvar::new()));
let resource_clone = resource.clone();
let handle1 = thread::spawn(move || {
let (lock, cvar) = &*resource_clone;
let mut data = lock.lock().unwrap();
while *data < 10 {
data = cvar.wait(data).unwrap();
}
println!("Thread1: data is now valid: {}", *data);
});
let handle2 = thread::spawn(move || {
let (lock, cvar) = &*resource;
let mut data = lock.lock().unwrap();
*data = 10;
cvar.notify_one();
println!("Thread2: data set and notified");
});
handle1.join().unwrap();
handle2.join().unwrap();
}
通过这些方法,可以有效地预防和检测Rust多线程系统中的死锁。