策略1:使用Mutex
- 实现方式:将
AtomicU64
用Mutex
包裹起来。每个线程在访问和修改AtomicU64
之前,先获取Mutex
的锁。例如:
use std::sync::{Arc, Mutex};
let shared_data = Arc::new(Mutex::new(AtomicU64::new(0)));
let thread_handles = (0..10).map(|_| {
let data = shared_data.clone();
std::thread::spawn(move || {
let mut guard = data.lock().unwrap();
guard.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
})
}).collect::<Vec<_>>();
for handle in thread_handles {
handle.join().unwrap();
}
- 优点:
- 简单直观,符合常规的同步思路。
- 能有效保证数据一致性,因为同一时间只有一个线程能获取锁并访问数据。
- 缺点:
- 性能瓶颈,锁的竞争会导致线程阻塞,尤其是在高并发场景下,频繁的加锁解锁操作会降低系统整体性能。
- 死锁风险,如果锁的获取顺序不当,可能会导致死锁。
策略2:使用RwLock(读写锁)
- 实现方式:如果读操作远多于写操作,可以用
RwLock
包裹AtomicU64
。读操作获取读锁,写操作获取写锁。例如:
use std::sync::{Arc, RwLock};
let shared_data = Arc::new(RwLock::new(AtomicU64::new(0)));
let read_threads = (0..10).map(|_| {
let data = shared_data.clone();
std::thread::spawn(move || {
let guard = data.read().unwrap();
let value = guard.load(std::sync::atomic::Ordering::SeqCst);
println!("Read value: {}", value);
})
}).collect::<Vec<_>>();
let write_thread = std::thread::spawn(move || {
let mut guard = shared_data.write().unwrap();
guard.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
});
for handle in read_threads {
handle.join().unwrap();
}
write_thread.join().unwrap();
- 优点:
- 读操作可以并发执行,提高了读性能,适用于读多写少的场景。
- 仍然能保证数据一致性,写操作时会独占锁,防止其他读写操作。
- 缺点:
- 实现相对复杂,需要区分读锁和写锁的使用场景。
- 写操作时仍然会阻塞所有读操作和其他写操作,若写操作频繁,性能提升有限。
策略3:使用无锁数据结构(如AtomicU64
自身的原子操作)
- 实现方式:直接利用
AtomicU64
提供的原子操作方法,如fetch_add
、fetch_sub
等,无需额外的锁机制。例如:
use std::sync::{Arc, atomic::AtomicU64};
let shared_data = Arc::new(AtomicU64::new(0));
let thread_handles = (0..10).map(|_| {
let data = shared_data.clone();
std::thread::spawn(move || {
data.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
})
}).collect::<Vec<_>>();
for handle in thread_handles {
handle.join().unwrap();
}
- 优点:
- 性能最高,因为没有锁的开销,适合高并发场景。
- 实现简单,利用
AtomicU64
的原子性保证数据一致性。
- 缺点:
- 功能相对有限,只能进行原子操作提供的特定功能,对于复杂操作难以直接实现。
- 原子操作的语义理解和使用需要一定的专业知识,容易出错。