面试题答案
一键面试可能遇到的问题 - ABA问题
- 问题描述: 在多线程环境下,当一个原子变量的值从A变为B,再变回A时,进行比较与交换(CAS)操作的线程可能无法察觉这个变化。因为CAS操作只关心当前值是否与预期值相同,而不关心这个值是否经历过变化。例如,线程1读取原子变量值为A,此时线程2将其值改为B,然后又改回A,当线程1执行CAS操作时,发现当前值是A,就认为没有发生变化,继续执行后续操作,但实际上这个值已经被修改过了,这可能导致一些逻辑错误。
Rust中避免ABA问题的方案 - 使用 AtomicPtr
配合 AtomicUsize
实现带版本号的CAS
-
实现思路: 通过引入一个版本号来记录原子变量的更改次数。每次原子变量的值发生变化时,版本号也相应增加。在进行CAS操作时,不仅要比较原子变量的值,还要比较版本号。只有当值和版本号都与预期相同时,才认为CAS操作成功。
-
示例代码:
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::ptr;
struct Resource {
data: i32,
}
fn main() {
let resource = Box::new(Resource { data: 0 });
let resource_ptr = Box::into_raw(resource);
let atomic_ptr = AtomicPtr::new(resource_ptr);
let atomic_version = AtomicUsize::new(0);
// 模拟多线程操作
let handle = std::thread::spawn(move || {
let expected_ptr = atomic_ptr.load(Ordering::SeqCst);
let expected_version = atomic_version.load(Ordering::SeqCst);
// 假设这里有一些复杂逻辑,期间其他线程可能修改了值
let new_resource = Box::new(Resource { data: 1 });
let new_ptr = Box::into_raw(new_resource);
if atomic_ptr.compare_and_swap(expected_ptr, new_ptr, Ordering::SeqCst) == expected_ptr
&& atomic_version.compare_and_swap(expected_version, expected_version + 1, Ordering::SeqCst) == expected_version
{
// CAS操作成功
let _ = unsafe { Box::from_raw(new_ptr) };
} else {
// CAS操作失败
}
});
let _ = handle.join();
// 记得释放原始指针
if let Some(ptr) = atomic_ptr.load(Ordering::SeqCst) {
let _ = unsafe { Box::from_raw(ptr) };
}
}
方案的优缺点
- 优点:
- 有效避免ABA问题:通过版本号的引入,使得线程能够感知到原子变量是否经历过变化,从而避免了ABA问题带来的潜在逻辑错误。
- 性能较好:与一些复杂的锁机制相比,CAS操作本身的性能较高,并且版本号的维护开销相对较小,在高并发场景下能够保持较好的性能。
- 缺点:
- 实现复杂度增加:相较于简单的原子变量操作,引入版本号增加了代码的复杂度,需要开发者更加小心地处理版本号的更新和比较逻辑。
- 额外的内存开销:需要额外的
AtomicUsize
来存储版本号,对于内存敏感的场景可能不太友好。