在大多数编程语言中,共享状态往往是“容易但危险”的操作。但在Rust中,情况正好相反——Rust让你必须“显式、安全、可控”地进行状态共享。尤其是在异步(async)场景下,很多开发者都会遇到需要在多个任务之间共享可变数据的需求。这时候,Arc<Mutex>几乎成为了Rust异步编程的标配组合。
一、为什么Rust让共享状态变得“难”?
Rust的所有权模型有三条铁律:
1. 不能同时拥有多个可变引用。
2. 不能随意在任务之间移动数据,除非你能保证安全。
3. 只要有并发访问,必须用同步机制保护数据。
这些规则看似麻烦,实际上是Rust帮你避免了并发编程中的各种“坑”,比如数据竞争、悬垂指针等。Rust不会让你侥幸写出不安全的代码,强制你用正确的方式处理共享状态。
二、Arc、Mutex和T的组合魔法
想象一下:T就是你要共享的数据(比如Vec、HashMap等),Mutex是“互斥锁”,保证同一时刻只有一个任务能修改数据。而Arc<Mutex>,就是“原子引用计数指针”,让多个异步任务可以安全地共享和加锁同一份数据。这种组合适用于多线程和异步场景,前提是你用对了工具。
三、实战:异步任务共享计数器
来看一个经典例子:
use std::sync::{Arc, Mutex};
use tokio::task;
#[tokio::main]
async fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let counter = Arc::clone(&counter);
let handle = task::spawn(async move {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
println!("Final count: {}", *counter.lock().unwrap());
}
和 js 的 Promise.all([]) 异步调用连几乎一样
在这个例子中,我们启动了5个异步任务,每个任务都安全地对同一个计数器进行加一操作。Mutex保证了同一时刻只有一个任务能修改计数器,Arc让多个任务拥有计数器的“所有权”。
四、警告!异步场景请用tokio::sync::Mutex
std::sync::Mutex虽然能用,但它是阻塞式的,会卡住线程。在异步代码里,如果你在锁内调用.await,整个异步调度器都可能被卡死。正确做法是用tokio::sync::Mutex,这个版本是“非阻塞”的,遇到锁竞争时会自动让出执行权,保证异步任务流畅运行。例如:
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::task;
#[tokio::main]
async fn main() {
let data = Arc::new(Mutex::new(vec![]));
let mut handles = vec![];
for i in 0..5 {
let data = Arc::clone(&data);
let handle = task::spawn(async move {
let mut vec = data.lock().await;
vec.push(i);
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
let result = data.lock().await;
println!("Final data: {:?}", *result);
}
五、最佳实践总结
在异步代码中,务必用tokio::sync::Mutex,避免阻塞线程。锁的作用域要尽量小,锁住后尽快释放,别让锁“霸占”资源。千万不要在持有锁的时候调用.await,这样容易死锁或拖慢性能。正确做法是先.await完再加锁处理数据。
ps: 这个和js中使用 await 还是 then 差不多的意思。
错误的做法
let mut lock = shared.lock().await;
do_async_work().await;
lock.push(...);
正确的做法
do_async_work().await;
let mut lock = shared.lock().await;
lock.push(...);
Arc<Mutex>不是万能钥匙,只有多任务需要共同修改状态、消息传递不方便时才用。很多时候,Rust的mpsc、broadcast等消息通道更高效。
六、结语
Rust并不禁止你共享状态,而是教会你如何安全地共享。只要掌握Arc<Mutex>的正确用法,你就能写出既安全又高性能的异步程序。别忘了,锁用得好,项目少烦恼!