Rust 异步共享状态实战:用 tokio::sync 的 Mutex、RwLock 与 broadcast 协作多任务
test12026-10-073 次阅读
为什么不能用标准库的同步原语
在标准库里,std::sync::Mutex 的 lock 会阻塞当前线程。但在 tokio 的异步运行时里,一个任务阻塞线程就等于卡住整个线程上调度的一批任务,代价很高。tokio::sync 提供了一套可"await"的异步版本:锁的争用不会阻塞线程,而是让出执行权,等锁可用再恢复。共享状态一旦要在 .await 点之间流动,就该用异步原语。
async Mutex 保护计数
下面用 tokio::sync::Mutex 包一个计数器,多个任务并发加一。注意锁要在 async 块里 .lock().await,临界区尽量小。
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let counter = Arc::new(Mutex::new(0u64));
let mut handles = Vec::new();
for _ in 0..10 {
let c = counter.clone();
handles.push(tokio::spawn(async move {
let mut g = c.lock().await;
*g += 1;
}));
}
for h in handles {
h.await.unwrap();
}
println!("count = {}", *counter.lock().await);
}
RwLock:读多写少更友好
配置、路由表这类"偶尔写、频繁读"的数据,用 RwLock 可以让多个读者同时持有,只在写入时独占。tokio 的 RwLock 同样是可 await 的。
use tokio::sync::RwLock;
#[derive(Clone, Default)]
struct Config {
max_connections: u32,
}
async fn read_config(cfg: &RwLock<Config>) -> u32 {
cfg.read().await.max_connections // 多个读者可并发进入
}
async fn update_config(cfg: &RwLock<Config>, v: u32) {
let mut g = cfg.write().await; // 写入时独占
g.max_connections = v;
}
broadcast:一份消息推给多个订阅者
配置热推送、行情广播这类场景,一个发布者要把同一条消息发给所有在线订阅者。tokio::sync::broadcast 是多方订阅、后订阅者收不到历史消息的广播频道,非常适合"服务端变更通知客户端"。
use std::sync::Arc;
use tokio::sync::{broadcast, Mutex, RwLock};
#[derive(Clone, Default)]
struct Config {
max_connections: u32,
}
#[derive(Clone)]
struct AppState {
hits: Mutex<u64>,
config: RwLock<Config>,
tx: broadcast::Sender<Config>,
}
#[tokio::main]
async fn main() {
let (tx, _rx) = broadcast::channel(16);
let state = Arc::new(AppState {
hits: Mutex::new(0),
config: RwLock::new(Config::default()),
tx,
});
// 读端:每个连接都 subscribe 一份,互不影响
let mut rx = state.tx.subscribe();
let push = tokio::spawn({
let tx = state.tx.clone();
async move {
tx.send(Config { max_connections: 100 }).unwrap();
}
});
push.await.unwrap();
let updated = rx.recv().await.unwrap();
println!("new max_connections = {}", updated.max_connections);
}
选型的几个提醒
- 只在需要跨 .await 点共享、且真有并发访问时才上 tokio::sync::Mutex;若只是把所有权 move 进任务,Arc 都不需要。
- broadcast 的接收端会滞后丢消息:容量满后最旧的消息被覆盖,慢消费者可能拿不到。容量按峰值推送速率估算。
- 别把 std::sync::Mutex 长期持锁跨过 .await,那会造成线程被真实阻塞;异步锁跨 await 是安全的。
- 读多写少优先 RwLock;读写都频繁且要整体替换的,用 Arc<RwLock<T>> 或原子指针模式更省。
小结
异步世界的共享状态要用异步原语。tokio::sync 的 Mutex 管独占修改,RwLock 优化读多写少,broadcast 解决一对多通知。三者配合,就能在 await 横行的并发代码里既保住数据一致,又不拖垮整个运行时。
T
test1
文章作者
为什么不能用标准库的同步原语 在标准库里,std::sync::Mutex 的 lock 会阻塞当前线程。但在 tok...
- 分类
- 技术
- 发布时间
- 2026-10-07
- 字数
- 约 2551 字
- 阅读
- 3 次
