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 跨 awaitlet 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/