rust语言学习笔记(指针八)Mutex<T>(跨线程安全的RefCell<T>)

Mutex<T>(互斥锁,Mutual Exclusion)是标准库 std::sync 模块提供的核心同步原语。它的主要作用是‌在多线程环境下保护共享数据,确保同一时刻只有一个线程可以访问或修改该数据‌,从而避免数据竞争(Data Race)。

8.1 Mutex::new(data):创建Mutex

  • 功能 ‌:创建一个新的 Mutex,包裹初始数据。
  • 用途‌:初始化共享状态。

8.2 .lock():阻塞获取锁

  • 功能 ‌:尝试获取互斥锁。如果锁当前被其他线程持有,当前线程会‌阻塞‌(进入睡眠状态),直到锁被释放。
  • 用途‌:这是最常用的方法,适用于必须获取锁才能继续执行的场景。
  • 注意事项 ‌:
    • 返回的 MutexGuard 实现了 DerefDerefMut,可以直接操作内部数据。
    • MutexGuard 离开作用域时,析构函数会自动释放锁。
    • 如果前一个持有锁的线程发生了恐慌(Panic),锁会被标记为"中毒"(Poisoned),此时 .lock() 会返回 Err。通常使用 .unwrap() 处理,但在生产环境中建议妥善处理 PoisonError
rust 复制代码
use std::sync::Mutex;
fn main() {
    let v = Mutex::new(1);

    // 1. 获取锁并修改值
    let mut v1 = v.lock().unwrap();
    *v1 += 2;
    println!("{:?}", v1);
    
    drop(v1); // 释放锁

    // 2. 再次获取锁
    match v.lock() {
        Ok(mut v2) => {
            *v2 += 3;
            println!("{:?}", v2)
        }
        Err(e) => {
            println!("锁中毒: {:?}", e);
        }
    }
}

8.3 .try_lock():非阻塞尝试获取锁

  • 功能 ‌:尝试立即获取锁。如果锁可用,则获取并返回 Ok(Guard);如果锁已被占用,则‌立即返回错误‌,不会阻塞当前线程。
  • 用途‌:适用于需要避免死锁、实现自定义重试逻辑、或在无法获取锁时需要执行其他备用逻辑的场景。
rust 复制代码
match v.try_lock() {
    Ok(mut v3) => {
        *v3 += 4;
        println!("{:?}", v3)
    }
    Err(e) => match e {
        std::sync::TryLockError::Poisoned(_) => {
            println!("锁中毒: {:?}", e);
        }
        std::sync::TryLockError::WouldBlock => {
            println!("锁已被占用: {:?}", e);
        }
    },
}

8.4 .into_inner():消费 Mutex 提取数据

  • 功能 ‌:消费(Consume)Mutex 本身,返回内部包裹的数据 T

  • 用途‌:

    • 当不再需要多线程同步,希望将数据转移出来时使用。
    • 常用于程序结束前获取最终结果,或在单线程上下文中解开包装。
  • 注意 ‌:调用后 Mutex 实例将被销毁,无法再使用。

rust 复制代码
 match v.into_inner() {
     Ok(mut v4) => {
         v4 += 5;
         println!("{:?}", v4)
     }
     Err(e) => {
         println!("锁中毒: {:?}", e);
     }
}
// v 已销毁,无法再使用

8.5 .get_mut():无锁获取可变引用

  • 功能 ‌:获取内部数据的可变引用 &mut T

  • 用途‌:

    • 无需加锁 ‌。因为该方法要求借用 &mut self,编译器静态保证了此时没有其他线程或守卫持有该锁。
    • 适用于初始化阶段、单线程环境,或在确定没有并发访问时的性能优化。
  • 注意 ‌:如果在多线程环境下错误地使用(例如通过 unsafe 绕过检查),会导致数据竞争。

rust 复制代码
let mut v = Mutex::new(1); 

match v.get_mut() {
    Ok(vv) => {
        *vv += 1;
        println!("{:?}", vv);
    }
    Err(e) => {
        println!("锁中毒: {:?}", e);
    }
}

8.6 PoisonError::into_inner():恢复中毒数据

所有返回 LockResult 的方法(lock, try_lock, into_inner, get_mut)都可能返回 PoisonError

  • 恢复数据 ‌:可以通过 PoisonError::into_inner() 从错误中提取出 MutexGuard 或数据,继续访问。这表示开发者承认数据可能不一致,但决定继续使用。
rust 复制代码
match v.lock() {
    Ok(mut v2) => {
        *v2 += 3;
        println!("{:?}", v2)
    }
    Err(e) => {
        println!("锁中毒: {:?}", e);
        let mut v0 = e.into_inner();  // 从错误中提取出 MutexGuard 或数据
        *v0 = 0;                      // 重新初始化数据为0
    }
}