MST

星途 面试题库

面试题:Rust自定义线程安全数据结构的设计与实现

假设需要设计一个线程安全的双端队列(deque)数据结构,用于多线程环境下高效地进出数据。请描述你设计该数据结构的思路,包括所选用的同步原语(如`Mutex`、`Condvar`等),如何处理并发访问的竞态条件,以及如何优化性能以避免不必要的锁竞争。请用Rust代码实现该数据结构的关键部分,并对代码进行详细解释。
36.4万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 选用同步原语:使用Mutex来保护双端队列的数据结构,确保同一时间只有一个线程能访问队列。同时,使用Condvar来处理线程等待和唤醒,以实现高效的线程同步。
  2. 处理竞态条件:在对双端队列进行插入和删除操作时,通过Mutex加锁来防止多个线程同时修改队列,避免数据竞争。当队列为空且有线程尝试取出元素时,使用Condvar让线程进入等待状态,直到队列中有新元素被插入。
  3. 优化性能:尽量缩小锁的粒度,只在对队列进行修改操作时加锁。对于读取操作,如果不需要保证绝对一致性,可以考虑使用RwLock来允许多个线程同时读。此外,可以使用parking_lot库中的MutexCondvar,它们在性能上比标准库中的同类产品更优。

Rust代码实现

use std::sync::{Arc, Mutex, Condvar};
use std::collections::VecDeque;

pub struct ThreadSafeDeque<T> {
    inner: Arc<(Mutex<VecDeque<T>>, Condvar)>,
}

impl<T> ThreadSafeDeque<T> {
    pub fn new() -> Self {
        ThreadSafeDeque {
            inner: Arc::new((Mutex::new(VecDeque::new()), Condvar::new())),
        }
    }

    pub fn push_front(&self, value: T) {
        let (lock, cvar) = &*self.inner;
        let mut queue = lock.lock().unwrap();
        queue.push_front(value);
        cvar.notify_one();
    }

    pub fn push_back(&self, value: T) {
        let (lock, cvar) = &*self.inner;
        let mut queue = lock.lock().unwrap();
        queue.push_back(value);
        cvar.notify_one();
    }

    pub fn pop_front(&self) -> Option<T> {
        let (lock, cvar) = &*self.inner;
        let mut queue = lock.lock().unwrap();
        while queue.is_empty() {
            queue = cvar.wait(queue).unwrap();
        }
        queue.pop_front()
    }

    pub fn pop_back(&self) -> Option<T> {
        let (lock, cvar) = &*self.inner;
        let mut queue = lock.lock().unwrap();
        while queue.is_empty() {
            queue = cvar.wait(queue).unwrap();
        }
        queue.pop_back()
    }
}

代码解释

  1. 结构体定义
    • ThreadSafeDeque结构体包含一个Arc,内部封装了一个元组,元组的第一个元素是Mutex<VecDeque<T>>,用于保护双端队列,第二个元素是Condvar,用于线程间的同步。
  2. 构造函数
    • new函数用于创建一个新的ThreadSafeDeque实例,初始化内部的VecDeque
  3. 入队操作
    • push_frontpush_back函数在双端队列的前端和后端插入元素。首先获取Mutex的锁,插入元素后,通过Condvar唤醒一个等待的线程(如果有)。
  4. 出队操作
    • pop_frontpop_back函数从双端队列的前端和后端取出元素。如果队列为空,线程会通过Condvar进入等待状态,直到被唤醒。当队列中有元素时,取出并返回元素。