MST

星途 面试题库

面试题:Rust可变变量在多线程环境下的复杂使用场景

在多线程编程中,Rust的可变变量使用需要特别注意。假设你有一个多线程程序,主线程需要创建多个子线程,这些子线程要共享并修改同一个可变变量。请描述如何使用Rust的同步原语(如Mutex等)来安全地实现这一场景,并编写完整且能正确运行的代码,同时分析代码中可能出现的死锁风险及如何避免。
27.8万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试
  1. 使用同步原语实现多线程共享可变变量
    • 在Rust中,可以使用Mutex(互斥锁)来保护共享的可变变量。Mutex确保在任何时刻只有一个线程可以访问被保护的数据,从而避免数据竞争。
    • 以下是实现代码:
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let shared_variable = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let shared_variable_clone = Arc::clone(&shared_variable);
        let handle = thread::spawn(move || {
            let mut data = shared_variable_clone.lock().unwrap();
            *data += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    let final_value = shared_variable.lock().unwrap();
    println!("Final value: {}", *final_value);
}
  1. 代码分析
    • Arc(原子引用计数)用于在多个线程间共享MutexArc允许在多个线程间安全地共享数据,因为它内部的引用计数操作是原子的。
    • Mutex包裹着共享的可变变量0lock方法会尝试获取锁,如果锁可用,则返回一个MutexGuard智能指针,它实现了DerefDerefMut,可以像普通引用一样访问和修改内部数据。当MutexGuard离开作用域时,锁会自动释放。
    • thread::spawn闭包中,首先克隆Arc,这样每个线程都有自己的Arc实例指向同一个Mutex。然后通过lock方法获取锁并修改内部的共享变量。
    • 主线程通过join等待所有子线程完成,最后获取锁并打印共享变量的最终值。
  2. 死锁风险及避免
    • 死锁风险
      • 死锁可能发生在多个线程以不同顺序获取多个锁的情况下。例如,如果线程A获取锁Mutex1,然后尝试获取锁Mutex2,而线程B获取锁Mutex2,然后尝试获取锁Mutex1,就可能发生死锁。在上述代码中,如果我们引入第二个Mutex,并且在不同线程中以不同顺序获取这两个Mutex,就会出现死锁风险。
    • 避免方法
      • 按固定顺序获取锁:确保所有线程以相同顺序获取多个锁。例如,如果有Mutex1Mutex2,所有线程都先获取Mutex1,再获取Mutex2
      • 使用try_lockMutex提供了try_lock方法,它尝试获取锁,如果锁不可用,立即返回Err。可以利用这个方法编写逻辑来避免死锁,例如在获取多个锁时,如果一个锁获取失败,可以释放已经获取的锁并重新尝试。
// 示例使用try_lock避免死锁
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let mutex1 = Arc::new(Mutex::new(0));
    let mutex2 = Arc::new(Mutex::new(0));

    let mutex1_clone = Arc::clone(&mutex1);
    let mutex2_clone = Arc::clone(&mutex2);

    let handle1 = thread::spawn(move || {
        loop {
            match (mutex1_clone.try_lock(), mutex2_clone.try_lock()) {
                (Ok(mut guard1), Ok(mut guard2)) => {
                    // 安全地访问和修改数据
                    *guard1 += 1;
                    *guard2 += 1;
                    break;
                }
                _ => {
                    // 锁获取失败,释放已获取的锁(如果有)
                    // 这里可以添加一些等待逻辑
                }
            }
        }
    });

    let handle2 = thread::spawn(move || {
        loop {
            match (mutex1.try_lock(), mutex2.try_lock()) {
                (Ok(mut guard1), Ok(mut guard2)) => {
                    *guard1 += 1;
                    *guard2 += 1;
                    break;
                }
                _ => {
                    // 锁获取失败,释放已获取的锁(如果有)
                    // 这里可以添加一些等待逻辑
                }
            }
        }
    });

    handle1.join().unwrap();
    handle2.join().unwrap();
}