保护全局唯一实例的Mutex在创建已存在元素时为何死锁?
Rust程序挂起问题:Mutex锁未释放排查与修复
你的Rust程序运行后出现挂起,核心原因是单线程下非递归Mutex的隐性死锁,结合LazyLock延迟初始化在panic场景下的状态异常导致锁获取阻塞。以下是具体分析和修复方案:
问题复现代码
use std::any::TypeId; use std::collections::HashSet; use std::marker::PhantomData; use std::sync::{LazyLock, Mutex}; use unique::Unique; static ID_SET: LazyLock<Mutex<HashSet<TypeId>>> = LazyLock::new(|| Mutex::new(HashSet::new())); mod unique { use super::*; pub struct Unique<O: 'static>(PhantomData<O>); impl<O: 'static> Unique<O> { pub fn id() -> TypeId { TypeId::of::<O>() } pub fn new() -> Option<Self> { let mut set = ID_SET.lock().unwrap(); dbg!(set.insert(Self::id())).then_some(Self(PhantomData)) } } impl<O: 'static> Drop for Unique<O> { fn drop(&mut self) { let mut set = ID_SET.lock().unwrap(); (!set.remove(&Self::id())).then(|| panic!("duplicity detected")); } } } fn main() { struct TheOneRing; let the_one_ring = Unique::<TheOneRing>::new().unwrap(); let the_two_ring = Unique::<TheOneRing>::new().unwrap(); panic!(); }
运行输出
[src/main.rs:24:13] set.insert(Self::id()) = true [src/main.rs:24:13] set.insert(Self::id()) = false
问题分析
程序挂起发生在unwrap()触发panic后的栈展开阶段:
- 第二个
new()调用返回None,unwrap()触发panic,程序进入栈展开流程,尝试销毁已创建的the_one_ring实例。 - 在
Unique的Drop实现中,尝试获取ID_SET的锁,但由于LazyLock的延迟初始化特性,panic场景下静态Mutex的内部状态异常,导致当前线程误以为锁已被持有,从而无限阻塞在lock()调用上。 - 标准库
Mutex是非递归锁,单线程下重复锁会直接死锁,这是挂起的直接触发点。
修复方案
方案1:移除LazyLock,直接初始化静态Mutex
避免延迟初始化在panic时的状态异常,直接在程序启动时初始化Mutex:
use std::any::TypeId; use std::collections::HashSet; use std::marker::PhantomData; use std::sync::Mutex; use unique::Unique; // 直接初始化静态Mutex,无需延迟加载 static ID_SET: Mutex<HashSet<TypeId>> = Mutex::new(HashSet::new()); mod unique { use super::*; pub struct Unique<O: 'static>(PhantomData<O>); impl<O: 'static> Unique<O> { pub fn id() -> TypeId { TypeId::of::<O>() } pub fn new() -> Option<Self> { let mut set = ID_SET.lock().unwrap(); dbg!(set.insert(Self::id())).then_some(Self(PhantomData)) } } impl<O: 'static> Drop for Unique<O> { fn drop(&mut self) { let mut set = ID_SET.lock().unwrap(); if !set.remove(&Self::id()) { panic!("duplicity detected"); } } } } fn main() { struct TheOneRing; let the_one_ring = Unique::<TheOneRing>::new().unwrap(); let the_two_ring = Unique::<TheOneRing>::new().unwrap(); panic!(); }
方案2:使用递归Mutex(依赖parking_lot库)
如果必须保留延迟初始化,使用支持递归锁的parking_lot::Mutex避免单线程死锁:
- 添加
parking_lot依赖到Cargo.toml:
[dependencies] parking_lot = "0.12"
- 修改代码:
use std::any::TypeId; use std::collections::HashSet; use std::marker::PhantomData; use std::sync::LazyLock; use parking_lot::Mutex; // 使用支持递归锁的Mutex use unique::Unique; static ID_SET: LazyLock<Mutex<HashSet<TypeId>>> = LazyLock::new(|| Mutex::new(HashSet::new())); mod unique { use super::*; pub struct Unique<O: 'static>(PhantomData<O>); impl<O: 'static> Unique<O> { pub fn id() -> TypeId { TypeId::of::<O>() } pub fn new() -> Option<Self> { let mut set = ID_SET.lock(); dbg!(set.insert(Self::id())).then_some(Self(PhantomData)) } } impl<O: 'static> Drop for Unique<O> { fn drop(&mut self) { let mut set = ID_SET.lock(); if !set.remove(&Self::id()) { panic!("duplicity detected"); } } } } fn main() { struct TheOneRing; let the_one_ring = Unique::<TheOneRing>::new().unwrap(); let the_two_ring = Unique::<TheOneRing>::new().unwrap(); panic!(); }
内容的提问来源于stack exchange,提问作者hkBst
相关产品推荐
相关产品推荐

