728 字
4 分钟
Arc<Mutex<T>> 之外:Rust 共享状态的几种形状

在 Rust 里要共享可变状态,第一反应通常是 Arc<Mutex<T>>。它正确、通用,也很容易变成瓶颈:所有访问串行化在一把锁上,锁的粒度往往比实际需要的大。

先分清状态的性质,再选结构。

计数器和标志位:原子就够了#

use std::sync::atomic::{AtomicU64, Ordering};
struct Metrics {
requests: AtomicU64,
errors: AtomicU64,
}
impl Metrics {
fn record(&self, ok: bool) {
self.requests.fetch_add(1, Ordering::Relaxed);
if !ok {
self.errors.fetch_add(1, Ordering::Relaxed);
}
}
}

计数器不需要互斥。Relaxed 对“只关心最终数值”的统计足够;如果这个计数要和别的状态建立 happens-before 关系,才需要更强的顺序。

转移所有权:channel 比锁更自然#

当共享状态的本质是“生产者把工作交给消费者”,用锁保护一个队列不如直接把所有权送过去:

enum Command {
Insert(String, Vec<u8>),
Flush,
}
let (tx, mut rx) = tokio::sync::mpsc::channel::<Command>(1024);
tokio::spawn(async move {
let mut map = std::collections::HashMap::new();
while let Some(cmd) = rx.recv().await {
match cmd {
Command::Insert(k, v) => { map.insert(k, v); }
Command::Flush => { /* 落盘 */ }
}
}
});

状态只在一个 task 里被修改,外部通过消息表达意图。锁消失了,争用变成了有界队列的背压。

读多写少:写时复制#

配置、路由表这类“读频率远高于写”的状态,可以用 Arc 加原子交换:

use std::sync::{Arc, atomic::{AtomicPtr, Ordering}};
struct Config { /* ... */ }
// 简化示意;生产里可以用 arc-swap 处理引用计数

arc-swap 这类库把“加载当前 Arc”和“发布新 Arc”做成了无锁操作。读者拿到的是一份不可变快照,写者构造新值后整体替换。代价是写操作要复制整份数据,适合写少且数据量可控的场景。

热点分片#

如果键空间很大、访问均匀,把一把锁拆成 N 把:

const SHARDS: usize = 32;
struct Sharded<K, V> {
shards: Vec<std::sync::RwLock<std::collections::HashMap<K, V>>>,
}
fn shard_of<K: std::hash::Hash>(key: &K) -> usize {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
key.hash(&mut h);
(h.finish() as usize) % SHARDS
}

每个键只落在固定分片,锁的争用随分片数下降。代价是遍历和全局操作要跨分片,复杂度上去了。

async 里的锁:别跨 await 持 std 锁#

std::sync::Mutex 的 guard 不是 Send,跨 await 持有会让 future 不能 Send;更糟的是,它可能阻塞整个 executor 线程。异步上下文里如果必须用互斥锁,用 tokio::sync::Mutex,并且只在确实需要跨 await 时用——它的开销比 std 锁大。

// 错误:guard 跨 await
let guard = std_mutex.lock().unwrap();
some_async().await;
// 正确:缩小临界区
{
let mut guard = std_mutex.lock().unwrap();
*guard += 1;
}
some_async().await;

结论#

Arc<Mutex<T>> 适合“状态小、访问不频繁、逻辑复杂”的场景。一旦它成为热点,先看状态是不是计数器(原子)、是不是消息(channel)、是不是读多写少(写时复制)、键是不是可分片(分片锁)。大多数锁争用不是锁本身的问题,而是把不该共享的东西共享了。

Arc<Mutex<T>> 之外:Rust 共享状态的几种形状
/posts/rust-shared-state/
作者
fantasy-peak
发布于
2026-07-02
许可协议
CC BY-NC-SA 4.0