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

如何避免多函数访问Mutex保护结构体不同字段时发生死锁?

如何避免多函数访问Mutex保护的结构体不同字段时的死锁

先看你提供的示例代码,它会直接触发死锁:

use std::sync::Mutex;

#[derive(Default)]
struct InnerData {
    ia: i32,
    ib: i32,
}

#[derive(Default)]
struct OuterData {
    oa: i32,
    ob: Mutex<InnerData>,
}

impl OuterData {
    fn foo(&self) {
        println!("fn foo begin");
        let b_mg = &mut self.ob.lock().unwrap();
        b_mg.ib = 5;
        println!("fn foo end");
    }
}

fn main() {
    let mut outer_data: OuterData = Default::default();
    let b_mg = &mut outer_data.ob.lock().unwrap();
    b_mg.ia = 3;

    if b_mg.ia == 3 {
        outer_data.foo();
    }
}

死锁原因

Rust标准库的Mutex是不可重入的——同一线程不能在持有锁的情况下再次获取同一个锁。示例里main函数已经通过b_mg持有了outer_data.ob的锁,调用foo时又尝试获取这个锁,线程会阻塞等待自己释放锁,最终死锁。

解决方法

1. 缩小锁的持有范围

最直接的方式是在不需要锁的时候尽早释放,确保调用其他函数时当前线程没有持有目标锁:

use std::sync::Mutex;

#[derive(Default)]
struct InnerData {
    ia: i32,
    ib: i32,
}

#[derive(Default)]
struct OuterData {
    oa: i32,
    ob: Mutex<InnerData>,
}

impl OuterData {
    fn foo(&self) {
        println!("fn foo begin");
        let mut b_mg = self.ob.lock().unwrap();
        b_mg.ib = 5;
        println!("fn foo end");
    }
}

fn main() {
    let mut outer_data: OuterData = Default::default();
    {
        // 用代码块限制锁的作用域,离开后自动释放锁
        let mut b_mg = outer_data.ob.lock().unwrap();
        b_mg.ia = 3;
    }

    // 重新获取锁检查值,确认后释放再调用foo
    let b_mg = outer_data.ob.lock().unwrap();
    let should_call_foo = b_mg.ia == 3;
    drop(b_mg); // 手动释放锁

    if should_call_foo {
        outer_data.foo();
    }
}

通过代码块或drop手动释放锁,保证调用foo时锁处于未持有状态。

2. 使用可重入锁

如果业务逻辑确实需要同一线程多次获取同一个锁,可以用第三方库的可重入锁,比如parking_lot的ReentrantMutex:

首先在Cargo.toml添加依赖:

[dependencies]
parking_lot = "0.12"

修改后的代码:

use parking_lot::ReentrantMutex;

#[derive(Default)]
struct InnerData {
    ia: i32,
    ib: i32,
}

#[derive(Default)]
struct OuterData {
    oa: i32,
    ob: ReentrantMutex<InnerData>,
}

impl OuterData {
    fn foo(&self) {
        println!("fn foo begin");
        let mut b_mg = self.ob.lock();
        b_mg.ib = 5;
        println!("fn foo end");
    }
}

fn main() {
    let mut outer_data: OuterData = Default::default();
    let mut b_mg = outer_data.ob.lock();
    b_mg.ia = 3;

    if b_mg.ia == 3 {
        outer_data.foo(); // 同一线程可重复获取锁,不会死锁
    }
}

注意:可重入锁有性能开销,且容易掩盖设计上的冗余锁问题,非必要不建议使用。

3. 拆分锁的粒度

如果结构体的不同字段可以独立访问,不要用一个大锁保护整个结构体,而是给每个字段单独加锁:

use std::sync::Mutex;

#[derive(Default)]
struct InnerData {
    ia: Mutex<i32>,
    ib: Mutex<i32>,
}

#[derive(Default)]
struct OuterData {
    oa: i32,
    ob: InnerData,
}

impl OuterData {
    fn foo(&self) {
        println!("fn foo begin");
        let mut ib_mg = self.ob.ib.lock().unwrap();
        *ib_mg = 5;
        println!("fn foo end");
    }
}

fn main() {
    let mut outer_data: OuterData = Default::default();
    let mut ia_mg = outer_data.ob.ia.lock().unwrap();
    *ia_mg = 3;

    if *ia_mg == 3 {
        outer_data.foo(); // 这里锁的是ib,和当前持有的ia锁不冲突
    }
}

这种方式不仅避免了死锁,还能提升并发性能——不同字段的锁相互独立,不会阻塞彼此的访问。

通用死锁规避原则

  • 锁的持有时间越短越好,尽量缩小锁的作用域;
  • 同一线程不要重复获取不可重入锁,确有需要再用可重入锁;
  • 多锁场景下,严格按照固定顺序获取锁,避免循环等待;
  • 拆分锁粒度,避免用大锁保护多个独立资源。

内容的提问来源于stack exchange,提问作者Harry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 06:50:01