我正在开发一个 Rust 应用程序,其中有一个与 PacketManager 交互的 BusDevice。在多线程环境中尝试锁定一个 Mutex 时,我遇到了死锁。应用程序被卡在锁定 Mutex 的那一行,再也无法继续。
详细描述:
在我的 Rust 应用程序中,我有一个使用 PacketManager 发送确认数据包的 BusDevice。BusDevice 中的 send_ack 方法会生成一个新线程来处理确认,但在试图获取 PacketManager 的 Mutex 上的锁时似乎会挂起。
以下是我的 BusDevice 实现的相关部分:
use std::sync::{Arc, Mutex};
pub struct BusDevice {
packet_manager:Arc<Mutex<PacketManager>>、
// 其他字段...
}
impl BusDevice {
pub fn send_ack(&self, dest: u8, seq: u8) {
let packet_manager = Arc::clone(&self.packet_manager);
std::thread::spawn(move || {
让 pm = packet_manager.lock();
匹配 pm {
Ok(mut pm) => {
pm.send_ack(0, dest, seq);
}
Err(e) => {
println!("Failed to lock PacketManager: {:?}", e);
}
}
})
.join()
.expect("Thread join failed");
}
}
而 PacketManager 的结构如下:use std::sync::{Arc, Mutex};
使用 serialport::TTYPort;
pub struct PacketManager {
serial_port:Arc<Mutex<TTYPort>>、
// 其他字段...
}
impl PacketManager {
pub fn send_ack(&mut self, src: u8, dest: u8, seq: u8) { // 逻辑发送确认数据包。
// 发送确认数据包的逻辑
}
}
我所尝试的:我已确保代码的其他部分不会长时间锁定。 我简化了 send_ack 方法以隔离问题,但问题依然存在。 我添加了打印语句来跟踪执行情况,结果证实应用程序在 packet_manager.lock() 处卡住了。
预期行为:
生成的线程应获取 PacketManager 上的锁,发送确认数据包,然后释放锁,而不会导致死锁。
代码中死锁的原因是在 send_ack
方法中使用了 thread::spawn
和 join
的组合方式。
问题在于:
send_ack
方法生成了一个新线程来处理确认数据包。- 在新线程中,代码尝试获取
packet_manager
的锁。 - 同时,
send_ack
方法调用join
方法等待新线程结束。
这会导致死锁,因为:
- 新线程持有
packet_manager
的锁,直到它完成发送确认数据包的操作。 - 主线程(调用
send_ack
的线程)在join
方法处等待新线程结束,而新线程需要先释放packet_manager
的锁才能结束。
解决方案:
要解决这个问题,需要避免在持有锁的情况下等待线程结束。一种方法是将 join
操作移出 send_ack
方法:
use std::sync::{Arc, Mutex};
pub struct BusDevice {
packet_manager: Arc<Mutex<PacketManager>>,
// 其他字段...
}
impl BusDevice {
pub fn send_ack(&self, dest: u8, seq: u8) {
let packet_manager = Arc::clone(&self.packet_manager);
std::thread::spawn(move || {
let pm = packet_manager.lock();
match pm {
Ok(mut pm) => {
pm.send_ack(0, dest, seq);
}
Err(e) => {
println!("Failed to lock PacketManager: {:?}", e);
}
}
});
// 不要在这里调用 join()
}
}
这样,主线程就不会阻塞等待新线程结束,从而避免了死锁。
其他建议:
- 考虑使用通道(channel)在主线程和工作线程之间传递数据,而不是共享
Arc<Mutex<PacketManager>>
。这样可以简化代码并提高性能。 - 评估是否真的需要为每个确认数据包创建一个新线程。如果发送确认数据包的操作比较轻量级,可以考虑将这些操作放到一个线程池中执行。