面试题答案
一键面试可能导致死锁的场景分析
- 循环依赖:进程 A 等待进程 B 通过通道发送的数据,而进程 B 又等待进程 A 通过另一个通道发送的数据,形成循环等待。例如,进程 A 持有资源 R1 并等待资源 R2,进程 B 持有资源 R2 并等待资源 R1。
- 资源分配不当:多个进程竞争有限的通道资源,如果分配策略不合理,可能导致某些进程一直无法获取所需资源而陷入死锁。
预防或解决死锁的方法
- 资源分配图算法:可以使用如银行家算法等资源分配图算法,在分配资源(通道数据等)之前检查是否会导致死锁。在 Rust 中可通过实现类似算法逻辑来确保资源分配的安全性。
- 打破循环依赖:调整进程交互逻辑,避免形成循环等待的情况。例如,可以重新设计进程间的通信顺序,确保不会出现相互等待的环。
- 超时机制:为每个通道操作设置超时。如果在规定时间内未完成操作,则认为出现异常,释放已占用资源并尝试重新执行或进行其他处理。
代码示例
- 使用超时机制解决死锁
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use std::time::Duration;
fn main() {
let (tx1, rx1): (Sender<i32>, Receiver<i32>) = channel();
let (tx2, rx2): (Sender<i32>, Receiver<i32>) = channel();
let handle1 = thread::spawn(move || {
if let Ok(data) = rx2.recv_timeout(Duration::from_secs(1)) {
println!("Thread 1 received: {}", data);
tx1.send(data + 1).unwrap();
} else {
println!("Thread 1: Timeout occurred, releasing resources...");
}
});
let handle2 = thread::spawn(move || {
if let Ok(data) = rx1.recv_timeout(Duration::from_secs(1)) {
println!("Thread 2 received: {}", data);
tx2.send(data + 1).unwrap();
} else {
println!("Thread 2: Timeout occurred, releasing resources...");
}
});
tx1.send(1).unwrap();
handle1.join().unwrap();
handle2.join().unwrap();
}
在上述代码中,两个线程通过通道进行通信,并设置了 1 秒的超时时间。如果在规定时间内未能接收到数据,线程会打印超时信息并释放资源,从而避免死锁。
- 打破循环依赖示例
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
fn main() {
let (tx1, rx1): (Sender<i32>, Receiver<i32>) = channel();
let (tx2, rx2): (Sender<i32>, Receiver<i32>) = channel();
let handle1 = thread::spawn(move || {
let data = 1;
tx1.send(data).unwrap();
if let Ok(result) = rx2.recv() {
println!("Thread 1 received: {}", result);
}
});
let handle2 = thread::spawn(move || {
if let Ok(data) = rx1.recv() {
let new_data = data + 1;
tx2.send(new_data).unwrap();
}
});
handle1.join().unwrap();
handle2.join().unwrap();
}
此代码通过重新设计线程间的通信顺序,先由 thread1
发送数据,thread2
接收并处理后再返回数据,打破了可能出现的循环依赖,从而避免死锁。