1. 选择合适的并发原语
- 锁:
- Mutex:适用于需要可变访问的情况。例如,在多线程环境下维护一个共享的计数器。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
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!("Final counter value: {}", *counter.lock().unwrap());
}
- **RwLock**:读多写少场景下性能更好。假设我们有一个共享的配置文件,大部分线程只是读取配置,偶尔有线程更新配置。
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let config = Arc::new(RwLock::new(String::from("default config")));
let mut handles = vec![];
for _ in 0..5 {
let config = Arc::clone(&config);
handles.push(thread::spawn(move || {
let read_config = config.read().unwrap();
println!("Read config: {}", read_config);
}));
}
for _ in 0..2 {
let config = Arc::clone(&config);
handles.push(thread::spawn(move || {
let mut write_config = config.write().unwrap();
*write_config = String::from("new config");
}));
}
for handle in handles {
handle.join().unwrap();
}
let final_config = config.read().unwrap();
println!("Final config: {}", final_config);
}
- 原子类型:
- AtomicUsize:适用于简单的原子操作,如无锁计数器。在不需要复杂锁逻辑的情况下,原子类型性能更高。
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
fn main() {
let counter = AtomicUsize::new(0);
let mut handles = vec![];
for _ in 0..10 {
let counter = &counter;
handles.push(thread::spawn(move || {
counter.fetch_add(1, Ordering::SeqCst);
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Final counter value: {}", counter.load(Ordering::SeqCst));
}
2. 优化数据结构
- 选择无锁数据结构:如
crossbeam::queue::MsQueue
,这是一个无锁的多生产者 - 多消费者队列。当多个线程需要高效地在队列中进出数据时,无锁队列能避免锁竞争带来的性能损耗。
use crossbeam::queue::MsQueue;
use std::thread;
fn main() {
let queue = MsQueue::new();
let mut handles = vec![];
for _ in 0..5 {
let queue = queue.clone();
handles.push(thread::spawn(move || {
queue.push(1);
}));
}
for _ in 0..3 {
let queue = queue.clone();
handles.push(thread::spawn(move || {
if let Some(_) = queue.pop() {
println!("Popped an item");
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
- 数据本地化:尽量减少跨线程共享的数据量,将数据分配到各个线程本地,减少同步开销。例如,在并行计算场景下,每个线程处理自己的数据块,最后再合并结果。
3. 利用Rust的所有权系统
- 移动语义:通过移动所有权,确保每个数据在某一时刻只有一个所有者,避免数据竞争。例如,在多线程间传递数据时,可以使用
std::thread::spawn
的闭包捕获所有权。
use std::thread;
fn main() {
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Thread got data: {:?}", data);
});
handle.join().unwrap();
}
- 生命周期管理:Rust的借用检查器会在编译时检查引用的生命周期,确保不会出现悬空引用,从而提高并发安全性。例如,在使用锁时,确保锁的持有时间合理,避免死锁和不必要的阻塞。