MST

星途 面试题库

面试题:Rust商店示例的复杂线程安全设计之资源竞争处理

在Rust商店示例里,存在多个线程对商品的订单处理操作,这些操作涉及读取库存、修改库存、记录订单等步骤。由于操作步骤较多,可能会出现资源竞争导致数据不一致问题。请详细阐述如何设计线程安全的解决方案,包括使用哪些Rust的并发原语以及如何组织代码结构来避免死锁等问题。
24.8万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

使用的并发原语

  1. Mutex(互斥锁):用于保护共享资源,在任何时刻只允许一个线程访问商品库存和订单记录等共享数据。在Rust中,std::sync::Mutex 提供了这种功能。例如,将商品库存和订单记录包装在 Mutex 中,线程在访问前需要先获取锁。
use std::sync::Mutex;

let stock = Mutex::new(100); // 初始库存100
let order_log = Mutex::new(Vec::new());
  1. Arc(原子引用计数):用于在多个线程间安全地共享数据。当多个线程需要访问同一个 Mutex 包裹的数据时,Arc 可以用来持有 Mutex 的所有权,从而实现跨线程共享。
use std::sync::{Arc, Mutex};

let shared_stock = Arc::new(Mutex::new(100));
let shared_order_log = Arc::new(Mutex::new(Vec::new()));
  1. Condvar(条件变量):如果在订单处理过程中有需要等待某个条件满足的情况,比如库存充足时才处理订单,可以使用 Condvar。它与 Mutex 配合使用,线程可以在条件变量上等待,当条件满足时被唤醒。
use std::sync::{Arc, Condvar, Mutex};

let stock = Arc::new((Mutex::new(100), Condvar::new()));

代码结构设计

  1. 封装订单处理逻辑:将订单处理操作封装在一个函数或结构体方法中,这样可以将共享资源的访问和操作集中管理。
struct OrderProcessor {
    stock: Arc<Mutex<i32>>,
    order_log: Arc<Mutex<Vec<String>>>,
}

impl OrderProcessor {
    fn process_order(&self, order_amount: i32) {
        let mut guard = self.stock.lock().unwrap();
        while *guard < order_amount {
            guard = self.stock.condvar().wait(guard).unwrap();
        }
        *guard -= order_amount;
        self.stock.unlock();

        let mut log_guard = self.order_log.lock().unwrap();
        log_guard.push(format!("Order of {} items processed", order_amount));
    }
}
  1. 线程管理:使用 std::thread::spawn 创建线程,并将 OrderProcessor 实例通过 Arc 传递给新线程。
use std::thread;

let processor = OrderProcessor {
    stock: Arc::new(Mutex::new(100)),
    order_log: Arc::new(Mutex::new(Vec::new())),
};

let handle = thread::spawn(move || {
    processor.process_order(10);
});

避免死锁

  1. 固定锁获取顺序:在涉及多个锁的情况下,确保所有线程以相同的顺序获取锁。例如,如果线程需要同时获取库存锁和订单记录锁,始终先获取库存锁,再获取订单记录锁。
  2. 超时机制:使用 try_lock 方法尝试获取锁,并设置超时。如果在规定时间内未能获取锁,可以选择放弃操作或采取其他策略,避免无限期等待。
let mut guard = match self.stock.try_lock() {
    Ok(guard) => guard,
    Err(_) => return, // 获取锁失败,放弃操作
};
  1. 减少锁持有时间:尽量缩短持有锁的代码块,只在必要的操作时持有锁。例如,在修改库存后立即释放库存锁,然后再进行订单记录操作,以减少死锁风险。