在 Rust 里,实现共享状态并发的核心在于"所有权 + 类型系统"。不同于传统语言依靠运行时检查,Rust 把并发安全问题前置到编译阶段解决,通过严格的规则来避免数据竞争。
1. 共享状态并发的风险
在多线程环境中,多个线程同时访问共享数据时,可能会出现以下问题:
- 数据竞争(Data Race):多个线程同时读写同一数据,且至少有一个写操作,就会造成数据竞争。
- 不一致状态:若对数据的修改不是原子操作,可能会使数据处于中间状态,进而被其他线程读到。
2. Rust 的解决之道
Rust 主要通过以下几种方式来保障共享状态并发的安全性:
2.1 所有权与借用规则
- 同一时间只能有一个可变引用:这一规则在编译阶段就能防止数据竞争。
- 不可变引用可以共享:多个线程能够同时读取共享数据。
2.2 Sync 与 Send 特性
- Send:实现了 Send 特性的类型,可以安全地在线程间转移所有权。
- Sync:实现了 Sync 特性的类型,允许在多个线程中被共享。
2.3 同步原语
Rust 提供了多种同步原语:
rust
use std::sync::{Mutex, RwLock, Arc};
use std::thread;
fn main() {
// Arc 用于多线程间的引用计数
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
// Mutex 保证同一时间只有一个线程能修改数据
let mut num = counter.lock().unwrap(); *num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
3. 常用并发类型
3.1 Mutex(互斥锁)
- 作用:保证同一时间只有一个线程可以访问共享数据。
- 使用场景:适用于读写操作都需要互斥的场景。
3.2 RwLock(读写锁)
- 特点:允许多个读操作同时进行,但写操作是互斥的。
- 优势:在读多写少的场景下,能显著提升并发性能。
3.3 Atomic 类型
- 原理:基于 CPU 原子指令实现,无需加锁。
- 性能:在简单操作(如计数器)上,性能远超 Mutex。
rust
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let counter = Arc::new(AtomicUsize::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
// 使用原子操作,无需加锁
counter.fetch_add(1, Ordering::SeqCst);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", counter.load(Ordering::SeqCst));
}
4. 线程安全与生命周期
Rust 的类型系统会对线程安全进行严格检查:
- 在线程间传递引用时,必须保证引用的生命周期足够长。
- 使用
'static
生命周期或 Arc 来管理跨线程的生命周期。