You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

保护全局唯一实例的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后的栈展开阶段:

  1. 第二个new()调用返回None,unwrap()触发panic,程序进入栈展开流程,尝试销毁已创建的the_one_ring实例。
  2. 在Unique的Drop实现中,尝试获取ID_SET的锁,但由于LazyLock的延迟初始化特性,panic场景下静态Mutex的内部状态异常,导致当前线程误以为锁已被持有,从而无限阻塞在lock()调用上。
  3. 标准库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避免单线程死锁:

  1. 添加parking_lot依赖到Cargo.toml:
[dependencies]
parking_lot = "0.12"
  1. 修改代码:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 16:12:02