面试题答案
一键面试场景一:多线程间共享数据的安全读取
在多线程编程中,当多个线程需要读取共享数据,且该数据可能会被其他线程修改时,为了保证读取到的数据的一致性和完整性,需要使用原子加载操作。
代码示例
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
fn main() {
let shared_data = Arc::new(AtomicUsize::new(0));
let shared_data_clone = shared_data.clone();
let handle = thread::spawn(move || {
// 模拟其他线程修改数据
shared_data_clone.store(42, Ordering::SeqCst);
});
// 主线程读取数据
let value = shared_data.load(Ordering::SeqCst);
println!("Read value: {}", value);
handle.join().unwrap();
}
作用阐述
原子加载操作确保在读取共享数据时,不会读取到部分修改的数据。在这个例子中,Ordering::SeqCst
提供了最强的内存顺序保证,确保读取操作看到的是所有线程对该原子变量的修改的一致视图,避免了数据竞争导致的未定义行为。
场景二:实现无锁数据结构
在构建无锁数据结构(如无锁队列、无锁栈等)时,原子加载操作用于安全地获取数据结构内部状态,同时避免使用锁带来的性能开销。
代码示例
use std::sync::atomic::{AtomicUsize, Ordering};
struct LockFreeStack {
top: AtomicUsize,
}
impl LockFreeStack {
fn new() -> Self {
LockFreeStack { top: AtomicUsize::new(0) }
}
fn push(&self, value: usize) {
// 简化实现,这里仅示意原子操作的使用
let old_top = self.top.load(Ordering::Relaxed);
// 这里应实际更新栈的逻辑,假设这里简单加一模拟入栈
self.top.store(old_top + 1, Ordering::Release);
}
fn pop(&self) -> Option<usize> {
let old_top = self.top.load(Ordering::Acquire);
if old_top == 0 {
None
} else {
// 假设这里简单减一模拟出栈
self.top.store(old_top - 1, Ordering::Release);
Some(old_top - 1)
}
}
}
作用阐述
在无锁数据结构中,原子加载操作与原子存储操作配合,确保对数据结构状态的读取和修改是原子性的,不同线程之间可以安全地访问和修改数据结构,而无需使用锁,提高了并发性能。