当多个线程需要频繁读取、偶尔写入共享状态时,Arc<RwLock<T>> 能提供更高吞吐:读者并行、写者独占。本节总结模式、代码范式、基准方法与踩坑清单。


1. 何时选 Arc<RwLock>?

  • 多读少写(读远多于写)且读操作无副作用;
  • 写入需要独占,但频率低且粒度可控;
  • 读操作耗时较短,避免长时间占读锁。

不适合:

  • 写入频繁(写多读少)——更偏向 Arc<Mutex<T>>
  • 单线程——直接 RefCell<T> 即可;
  • 需要无锁读写——考虑分片/原子或更高级数据结构。

2. 基础用法

use std::sync::{Arc, RwLock};
use std::thread;
use std::collections::HashMap;

fn main() {
    let store: Arc<RwLock<HashMap<String, String>>> = Arc::new(RwLock::new(HashMap::new()));

    // 写线程
    {
        let s = Arc::clone(&store);
        thread::spawn(move || {
            let mut w = s.write().unwrap();
            w.insert("key".into(), "value".into());
        }).join().unwrap();
    }

    // 多个读线程
    let mut hs = vec![];
    for _ in 0..4 {
        let s = Arc::clone(&store);
        hs.push(thread::spawn(move || {
            let r = s.read().unwrap();
            r.get("key").cloned()
        }));
    }
    for h in hs { println!("{:?}", h.join().unwrap()); }
}

在这里插入图片描述


3. 设计模式

3.1 读锁短、写锁短

  • 锁外准备数据,在锁内只做最小工作;
  • 读操作尽量返回拷贝/克隆的小值或只读视图,避免长时间持锁。

3.2 分层/分片(Sharding)

  • 将状态按 key 范围划分多个 Arc<RwLock<Shard>>
  • 读常命中不同 shard,最大化并行度。

3.3 单写队列(Writer Thread)

  • 读线程只持读锁;
  • 写操作汇聚到单写线程,通过 channel 接收命令,减少写竞争。

4. 示例:计数器映射(读多写少)

use std::sync::{Arc, RwLock};
use std::collections::HashMap;
use std::thread;

struct CounterMap(Arc<RwLock<HashMap<String, usize>>>);
impl CounterMap {
    fn new() -> Self { Self(Arc::new(RwLock::new(HashMap::new()))) }
    fn inc(&self, k: &str) { let mut w = self.0.write().unwrap(); *w.entry(k.into()).or_default() += 1; }
    fn get(&self, k: &str) -> Option<usize> { let r = self.0.read().unwrap(); r.get(k).cloned() }
}

fn main() {
    let cm = CounterMap::new();
    let mut hs = vec![];
    for _ in 0..8 {
        let c = cm.0.clone();
        hs.push(thread::spawn(move || { let mut w = c.write().unwrap(); *w.entry("x".into()).or_default() += 1; }));
    }
    for h in hs { h.join().unwrap(); }
    println!("x={:?}", cm.get("x"));
}

在这里插入图片描述


5. 基准测试思路

  • 场景一:80% 读、20% 写;场景二:95% 读、5% 写;
  • 对比 Arc<Mutex<T>>Arc<RwLock<T>> 吞吐量;
  • 可用 criterion 做统计:
[dev-dependencies]
criterion = "0.5"
use criterion::{criterion_group, criterion_main, Criterion, black_box};

一般在读比例高时,RwLock 明显优于 Mutex;写比例升高后优势减弱。


6. 踩坑清单

  • 读锁里执行 IO 或长计算 → 读者长时间占锁,写者饥饿;
  • 读锁内再尝试获取写锁(或反之)→ 死锁风险,拆分代码;
  • 频繁写多读少还用 RwLock → 退化为慢 Mutex;
  • 读返回引用并长时间持有 → 可返回 Clone 的小值或复制,释放锁。

7. 进阶:parking_lot 锁替代

  • parking_lot::RwLock 在很多场景性能优于标准库;
  • API 基本兼容,细节可参阅其文档与 Bench。

8. 练习

  1. 将计数器映射改造为分片(16 分片),比较并发读写吞吐;
  2. 实现“单写队列”版本:读线程不加写锁,通过 channel 发写命令到专门写线程;
  3. 给状态增加“快照”功能:在读锁下复制到本地结构再计算,避免长锁;
  4. criterion 基准:Mutex vs RwLock vs parking_lot::RwLock

小结:在读多写少的共享状态下,Arc<RwLock<T>> 往往带来更好吞吐;合理的分层与锁粒度控制,比盲目使用某一种锁更关键。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐