在 Golang 中,开发者需要手动管理并发读写锁(如使用 sync.RWMutex
),这可能导致忘记加锁、解锁或死锁等问题。相比之下,Rust 通过多种机制在编译时就解决了大部分并发安全问题:
Rust 最独特的特性是其所有权系统,它在编译时强制执行以下规则: - 同一时刻,一个数据只能有一个所有者 - 对于同一数据,在同一时间: - 要么有多个不可变引用(读取) - 要么只有一个可变引用(写入)
这些规则在编译时就能防止数据竞争。
fn main() {
let mut data = vec![1, 2, 3];
// 可以同时有多个不可变引用
let reader1 = &data;
let reader2 = &data;
// 编译错误:不能同时有可变和不可变引用
// let writer = &mut data; // 如果取消注释,编译会失败
println!("{:?} {:?}", reader1, reader2);
// reader1和reader2不再使用后,可以创建可变引用
let writer = &mut data;
writer.push(4);
}
当需要在线程间共享数据时,Rust 提供了几种安全抽象:
use std::sync::{Mutex, RwLock};
use std::thread;
fn mutex_example() {
let counter = Mutex::new(0);
// 锁的获取和释放是通过RAII模式自动管理的
{
let mut num = counter.lock().unwrap();
*num += 1;
// 离开作用域时自动释放锁
}
}
fn rwlock_example() {
let data = RwLock::new(vec![1, 2, 3]);
// 读锁允许多个并发读取
{
let readers = data.read().unwrap();
println!("{:?}", *readers);
} // 读锁自动释放
// 写锁保证独占访问
{
let mut writer = data.write().unwrap();
writer.push(4);
} // 写锁自动释放
}
use std::sync::{Arc, Mutex};
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 || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
Rust 使用两个特殊的 trait 来确保类型的线程安全性:
- Send
:表示类型的所有权可以安全地在线程间传递
- Sync
:表示类型可以被多个线程同时引用
编译器会自动检查这些 trait 的实现,防止不安全的并发访问。
Rust 的优势在于将大部分并发安全问题转移到了编译时检查,而不是运行时。虽然这使得 Rust 代码有时更难编写(需要满足编译器的严格要求),但它能够在编译阶段就防止数据竞争和死锁等并发问题,提供了”零成本抽象”的并发安全保证。