Rust

2025年11月03日更新 13 人订阅
原价: ¥ 6 限时优惠
专栏简介

Rust并发安全基石:Mutex与RwLock深度解析

Rust并发安全基石:Mutex与RwLock深度解析在多线程编程中,如何安全、高效地共享和修改数据是一个永恒的核心难题。Rust通过其严格的所有权和借用检查机制,在编译期就为我们杜绝了大量数据竞争问题。但当多个线程确实需要访问同一份数据时,我们就必须借助强大的同步原语(Synchronizati

Rust并发安全基石:Mutex与RwLock深度解析

在多线程编程中,如何安全、高效地共享和修改数据是一个永恒的核心难题。Rust通过其严格的所有权和借用检查机制,在编译期就为我们杜绝了大量数据竞争问题。但当多个线程确实需要访问同一份数据时,我们就必须借助强大的同步原语(Synchronization Primitives)。

Mutex(互斥锁)和RwLock(读写锁)正是Rust标准库为此提供的两大并发安全基石。本文将通过详尽的原理讲解和可运行的代码实例,带你深入探索这两者的工作机制、核心区别、适用场景,并揭示其独特的“锁中毒”(Lock Poisoning)安全策略。在此之前,我们也会先回顾单线程环境下的内部可变性工具CellRefCell,为彻底理解并发安全打下坚实的基础。

本文内容

  • Cell<T>
  • RefCell<T>
  • Mutex
  • RwLock

Cell<T>

Cell<T>RefCell<T> 都实现了内部可变性模式。

内部可变性:通过不可变引用来修改其持有的值。

对于一个对象 T,只能存在以下两种情况之一:

  1. 若干个指向该对象的不可变引用 (&T)
  2. 一个指向该对象的可变引用 (&mut T)

Cell 只能用于单线程

  • 通过移动(move) 值的方式实现内部可变性
  • 无法获取到内部值的 &mut T
  • 无法直接获取内部的值,除非用别的值替换它
  • 确保不会有多个引用同时指向内部的值
  • 针对实现了 Copy 的类型,get 方法可通过复制的方式获取内部值
  • 针对实现了Default的类型,take 方法会将当前内部值替换为 Default::default(),并返回原来的值
  • 针对所有类型
    • Replace 方法,替换当前内部值,返回原来的内部值
    • into_inner 方法,消耗 (consume) 掉这个 Cell<T>,并返回内部值
    • set 方法,替换当前的内部值,丢弃原来的值
  • Cell<T> 一般用于简单类型(如数值),因为复制/移动不会太消耗资源,它无法获取内部类型的直接引用
  • 在可能得情况下应该优先使用Cell<T> 而不是其它的 Cell 类型
  • 对于较大的或者不可复制 (non-copy) 的类型,RefCell 更有优势

Cell<T>实操

use std::cell::Cell;

fn main() {
    let cell = Cell::new(5);
    assert_eq!(cell.get(), 5);

    assert_eq!(cell.replace(10), 5);
    assert_eq!(cell.get(), 10);

    let ten = cell.into_inner();
    assert_eq!(ten, 10);

    let cell = Cell::new(String::from("hello"));
    assert_eq!(cell.take(), "hello");
    assert_eq!(cell.take(), String::default());

    cell.set(String::from("world"));
    assert_eq!(cell.take(), "world");
}

运行

RustJourney/cell on  main [?] is 📦 0.1.0 via 🦀 1.89.0 
➜ cargo run
   Compiling cell v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/cell)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/cell`

RefCell<T>

  • Cell<T> 不同,RefCell<T> 允许直接借用它的内部值,但是会有一点运行时开销
  • RefCell<T> 不仅持有 T 的值,还持有一个计数器,用来追踪有多少个借用
    • 借用是在运行时被追踪的
    • Rust 的原生引用类型在编译时进行静态检查
  • borrow():可以获取对 RefCell 内部值的不可变引用 (&T)
  • borrow_mut():可以获取对 RefCell 内部值的可变引用 (&mut T)
  • 其它:try_borrow()try_borrow_mut()into_inner()replace()take()...
  • 借用规则:
    • 任意数量的不可变借用 (&T)
    • 或单个可变借用 (&mut T)
    • 如果违反规则则线程会 panic
  • 在同一作用域内,一个值要么可以有多个不可变借用(&T),要么只能有一个可变借用(&mut T)。

RefCell<T> 实操

use std::cell::RefCell;

fn main() {
    let rc = RefCell::new(5);
    println!("rc = {rc:#?}");

    {
        let five = rc.borrow();
        let five1 = rc.borrow();
        assert_eq!(*five, 5);
        assert_eq!(*five1, 5);
    }

    let mut f = rc.borrow_mut();
    *f += 10;
    assert_eq!(*f, 15);
    println!("f = {f:#?}");

    let v = rc.try_borrow();
    assert!(v.is_err());

    drop(f);

    // RefMut 实现了 Deref Trait
    *rc.borrow_mut() += 10;

    println!("rc = {rc:#?}");
}

运行

RustJourney/cell on  main [?] is 📦 0.1.0 via 🦀 1.89.0 
➜ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/cell`
rc = RefCell {
    value: 5,
}
f = 15
rc = RefCell {
    value: 25,
}

Mutex

Mutual Exclusion

互斥锁:一种用于保护共享数据的互斥原语。

  • 原语 (primitive):最基本、不可再分解的操作或机制
  • Mutex
    • 最常见的用于在线程间分享(可变)数据的工具
    • 只允许对数据的独占 (exclusive) 访问,临时阻塞同一时刻想要访问数据的其它线程

Mutex两种状态 (LOCKED UNLOCKED)

  • 访问数据前需要请求锁 (lock)

  • 处理完成时需要移除锁 (unlock)

  • 锁定 (locked)

  • 未锁定 (unlocked)

  • Mutex 锁定 Lock

  • Mutex 解锁 unlock

  • 这里的 unlock 是指等着 MutexGuard 走出作用域

  • 解锁的线程与锁定的线程应该是同一个

...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论