面试题答案
一键面试可能导致死锁的情况
- 双向依赖:两个线程通过通道相互等待对方发送的数据。例如:
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx1, rx1) = mpsc::channel();
let (tx2, rx2) = mpsc::channel();
thread::spawn(move || {
let data = rx2.recv().unwrap();
tx1.send(data).unwrap();
});
thread::spawn(move || {
let data = rx1.recv().unwrap();
tx2.send(data).unwrap();
});
}
在这个例子中,两个线程互相等待对方发送的数据,形成死锁。 2. 通道关闭前未处理所有数据:如果在通道关闭前,接收方没有处理完所有发送的数据,并且发送方还在等待接收方确认,可能导致死锁。例如:
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for i in 0..10 {
tx.send(i).unwrap();
}
// 这里没有等待接收方处理完数据就关闭通道
drop(tx);
});
let mut sum = 0;
for val in rx {
sum += val;
if sum > 20 {
// 这里没有处理完所有数据就退出循环,而发送方可能在等待接收方处理完数据
break;
}
}
}
排查死锁
- 使用
RUST_BACKTRACE=1
环境变量:运行程序时设置此环境变量,Rust会打印出线程的栈跟踪信息,帮助定位死锁发生的位置。例如:
RUST_BACKTRACE=1 cargo run
- 添加日志:在代码中合适的位置添加日志,记录线程的执行状态和通道操作,通过分析日志来发现死锁的原因。例如:
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx1, rx1) = mpsc::channel();
let (tx2, rx2) = mpsc::channel();
thread::spawn(move || {
println!("Thread 1: waiting to receive from rx2");
let data = rx2.recv().unwrap();
println!("Thread 1: received data from rx2, sending to tx1");
tx1.send(data).unwrap();
});
thread::spawn(move || {
println!("Thread 2: waiting to receive from rx1");
let data = rx1.recv().unwrap();
println!("Thread 2: received data from rx1, sending to tx2");
tx2.send(data).unwrap();
});
thread::sleep(Duration::from_secs(2));
}
通过日志可以看到线程卡在接收数据的地方,从而推断可能发生死锁。
解决死锁
- 打破双向依赖:重新设计线程间的通信逻辑,避免相互等待。例如,修改第一个双向依赖的例子为:
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx1, rx1) = mpsc::channel();
let (tx2, _rx2) = mpsc::channel();
thread::spawn(move || {
let data = 42;
tx1.send(data).unwrap();
tx2.send(data).unwrap();
});
let received_data = rx1.recv().unwrap();
println!("Received data: {}", received_data);
}
这样就避免了双向等待的情况。 2. 确保数据处理完毕:在关闭通道前,确保接收方已经处理完所有发送的数据。例如,修改第二个例子为:
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for i in 0..10 {
tx.send(i).unwrap();
}
drop(tx);
});
let mut sum = 0;
for val in rx {
sum += val;
}
println!("Sum: {}", sum);
}
这里接收方会处理完通道中的所有数据,避免了死锁。