MST
星途 面试题库

面试题:Rust原子操作比较和交换机制在多线程环境下的应用

假设有多个线程同时访问和修改一个共享的`AtomicUsize`类型的变量,使用比较和交换机制实现一个线程安全的栈数据结构。要求实现入栈(`push`)和出栈(`pop`)方法,确保在多线程环境下数据的一致性和正确性。请阐述你实现过程中如何处理CAS操作可能出现的竞争情况,以及如何利用Rust的内存模型保证线程安全。
43.4万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试
  1. 定义栈结构

    use std::sync::atomic::{AtomicUsize, Ordering};
    
    struct ThreadSafeStack {
        stack: Vec<usize>,
        top: AtomicUsize,
    }
    

    这里我们使用一个Vec<usize>来实际存储栈中的元素,AtomicUsize来表示栈顶的位置。

  2. 实现入栈方法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方法尝试更新栈顶位置。如果更新成功,说明当前线程成功获取到栈顶的控制权,将元素添加到栈中合适的位置。如果更新失败,说明其他线程已经修改了栈顶位置,我们重新尝试。

  3. 实现出栈方法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方法尝试更新栈顶位置。如果更新成功,返回栈顶元素。如果失败,重新尝试。

  4. 处理CAS操作可能出现的竞争情况

    • 我们使用loop循环来不断尝试compare_and_swap操作。只要操作失败(即返回值不等于当前期望的值),就意味着有其他线程在同时修改栈顶位置,我们重新获取当前栈顶位置并再次尝试,直到操作成功。
    • pushpop方法中,每次获取current_top后都要重新计算new_top,因为在循环过程中栈顶位置可能已经被其他线程修改。
  5. 利用Rust的内存模型保证线程安全

    • Rust的内存模型基于所有权系统和类型系统,确保内存安全和线程安全。
    • 在这个实现中,AtomicUsize类型提供了原子操作,通过使用合适的内存序(Ordering::RelaxedOrdering::AcquireOrdering::Release等)来保证内存可见性和操作顺序。
    • AtomicUsize的操作会按照指定的内存序进行,例如compare_and_swap操作使用Ordering::AcquireOrdering::Release,这有助于在多线程环境下保证数据的一致性。Acquire语义确保在读取AtomicUsize的值之后,之前的内存操作对当前线程是可见的;Release语义确保在修改AtomicUsize的值之前,之后的内存操作对其他线程是可见的。
    • 同时,Vec<usize>虽然不是线程安全的数据结构,但由于我们通过AtomicUsize来控制对其的访问,并且在每次访问Vec时都已经通过compare_and_swap确保了线程安全,所以整体栈结构在多线程环境下是线程安全的。