面试题答案
一键面试-
定义栈结构:
use std::sync::atomic::{AtomicUsize, Ordering}; struct ThreadSafeStack { stack: Vec<usize>, top: AtomicUsize, }
这里我们使用一个
Vec<usize>
来实际存储栈中的元素,AtomicUsize
来表示栈顶的位置。 -
实现入栈方法
push
:impl ThreadSafeStack { fn push(&mut self, value: usize) { loop { let current_top = self.top.load(Ordering::Relaxed); let new_top = current_top.checked_add(1).expect("Stack overflow"); if self.top.compare_and_swap(current_top, new_top, Ordering::Acquire) == current_top { if current_top >= self.stack.len() { self.stack.push(value); } else { self.stack[current_top] = value; } break; } } } }
在
push
方法中,我们首先获取当前栈顶位置current_top
,然后计算新的栈顶位置new_top
。接着使用compare_and_swap
方法尝试更新栈顶位置。如果更新成功,说明当前线程成功获取到栈顶的控制权,将元素添加到栈中合适的位置。如果更新失败,说明其他线程已经修改了栈顶位置,我们重新尝试。 -
实现出栈方法
pop
:impl ThreadSafeStack { fn pop(&mut self) -> Option<usize> { loop { let current_top = self.top.load(Ordering::Relaxed); if current_top == 0 { return None; } let new_top = current_top.checked_sub(1).expect("Stack underflow"); if self.top.compare_and_swap(current_top, new_top, Ordering::Release) == current_top { let value = self.stack[current_top - 1]; return Some(value); } } } }
在
pop
方法中,首先检查栈是否为空,如果为空则返回None
。否则获取当前栈顶位置current_top
,计算新的栈顶位置new_top
。同样使用compare_and_swap
方法尝试更新栈顶位置。如果更新成功,返回栈顶元素。如果失败,重新尝试。 -
处理CAS操作可能出现的竞争情况:
- 我们使用
loop
循环来不断尝试compare_and_swap
操作。只要操作失败(即返回值不等于当前期望的值),就意味着有其他线程在同时修改栈顶位置,我们重新获取当前栈顶位置并再次尝试,直到操作成功。 - 在
push
和pop
方法中,每次获取current_top
后都要重新计算new_top
,因为在循环过程中栈顶位置可能已经被其他线程修改。
- 我们使用
-
利用Rust的内存模型保证线程安全:
- Rust的内存模型基于所有权系统和类型系统,确保内存安全和线程安全。
- 在这个实现中,
AtomicUsize
类型提供了原子操作,通过使用合适的内存序(Ordering::Relaxed
、Ordering::Acquire
、Ordering::Release
等)来保证内存可见性和操作顺序。 AtomicUsize
的操作会按照指定的内存序进行,例如compare_and_swap
操作使用Ordering::Acquire
和Ordering::Release
,这有助于在多线程环境下保证数据的一致性。Acquire
语义确保在读取AtomicUsize
的值之后,之前的内存操作对当前线程是可见的;Release
语义确保在修改AtomicUsize
的值之前,之后的内存操作对其他线程是可见的。- 同时,
Vec<usize>
虽然不是线程安全的数据结构,但由于我们通过AtomicUsize
来控制对其的访问,并且在每次访问Vec
时都已经通过compare_and_swap
确保了线程安全,所以整体栈结构在多线程环境下是线程安全的。