- 优化方案
- 使用
Atomic
类型:Rust提供了std::sync::atomic
模块,Atomic
类型可以在不使用锁的情况下进行原子操作,非常适合这种多线程环境下对共享变量的读写。例如,使用AtomicUsize
来替代Mutex<i32>
。
compare_and_swap
操作:Atomic
类型提供了compare_and_swap
等原子操作方法。在我们的场景中,可以利用compare_and_swap
来确保在修改counter
时的原子性,避免数据竞争。
- 代码示例
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
let counter = AtomicUsize::new(0);
let mut handles = vec![];
for _ in 0..10 {
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
loop {
let current = counter_clone.load(Ordering::Relaxed);
if current >= 100 {
break;
}
let new = current + 1;
if counter_clone.compare_and_swap(current, new, Ordering::Relaxed) == current {
break;
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
- Rust特性及同步机制
Atomic
特性:Atomic
类型保证了对其值的操作是原子的,即不会被其他线程打断。不同的Ordering
参数控制了内存顺序,Relaxed
是最宽松的顺序,适用于我们这里只关心counter
值的一致性,而不关心其与其他内存操作的顺序关系的场景。
compare_and_swap
同步机制:这个操作会比较当前值和预期值,如果相等则将当前值替换为新值,并返回旧值。通过这种方式,我们可以在不使用锁的情况下,确保对counter
的修改是线程安全的。
- 正确性分析
- 数据一致性:由于使用了
Atomic
类型和compare_and_swap
操作,每次对counter
的读取和修改都是原子的,不会出现数据竞争的情况。因此,在并发场景下,counter
的值始终能保持一致,最终会达到100。
- 循环终止:每个线程在循环中检查
counter
的值,当counter
达到100时,线程会停止循环,保证所有线程都会在counter
达到100时结束。
- 性能提升点
- 减少锁开销:相比使用
Mutex
,Atomic
类型不需要获取锁,从而避免了锁带来的性能开销,特别是在高并发场景下,锁的争用会显著降低性能。
- 提高并发度:原子操作允许线程在没有锁的情况下并发执行,提高了程序的并发度,从而提升了整体性能。