MST

星途 面试题库

面试题:Rust并发编程中的死锁避免及RwLock高级应用

在一个复杂的Rust并发程序中,存在多个线程,部分线程需要频繁读取共享数据,部分线程需要修改共享数据。如何使用RwLock来优化读写性能?同时,描述在这种场景下可能产生死锁的情况,并说明如何避免死锁。请结合实际代码示例详细阐述。
45.5万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

使用RwLock优化读写性能

  1. 原理:RwLock(读写锁)允许同一时间有多个线程进行读操作,但只允许一个线程进行写操作。读操作之间不会相互阻塞,提高了并发读的性能。
  2. 代码示例
use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let data = Arc::new(RwLock::new(String::from("initial data")));

    let mut read_handles = vec![];
    for _ in 0..3 {
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let read_data = data_clone.read().unwrap();
            println!("Read data: {}", read_data);
        });
        read_handles.push(handle);
    }

    let write_handle = thread::spawn(move || {
        let mut write_data = data.write().unwrap();
        *write_data = String::from("new data");
        println!("Write data: {}", write_data);
    });

    for handle in read_handles {
        handle.join().unwrap();
    }
    write_handle.join().unwrap();
}

在上述代码中,多个读线程可以同时获取读锁读取数据,而写线程获取写锁时会阻塞其他读线程和写线程,直到写操作完成。

可能产生死锁的情况

  1. 情况描述:当多个线程以不同顺序获取多个RwLock时,可能会产生死锁。例如,线程A获取RwLock1的写锁,然后尝试获取RwLock2的写锁;而线程B获取RwLock2的写锁,然后尝试获取RwLock1的写锁。这样两个线程都会阻塞,形成死锁。
  2. 代码示例
use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let rwlock1 = Arc::new(RwLock::new(0));
    let rwlock2 = Arc::new(RwLock::new(0));

    let rwlock1_clone = Arc::clone(&rwlock1);
    let rwlock2_clone = Arc::clone(&rwlock2);

    let thread1 = thread::spawn(move || {
        let mut lock1 = rwlock1_clone.write().unwrap();
        println!("Thread 1 acquired lock 1");
        let lock2 = rwlock2_clone.write().unwrap();
        println!("Thread 1 acquired lock 2");
    });

    let thread2 = thread::spawn(move || {
        let mut lock2 = rwlock2.write().unwrap();
        println!("Thread 2 acquired lock 2");
        let lock1 = rwlock1.write().unwrap();
        println!("Thread 2 acquired lock 1");
    });

    thread1.join().unwrap();
    thread2.join().unwrap();
}

这个示例中,thread1thread2以不同顺序获取rwlock1rwlock2,很可能导致死锁。

避免死锁的方法

  1. 按固定顺序获取锁:所有线程都按照相同的顺序获取多个锁。例如,在上述代码中,所有线程都先获取rwlock1,再获取rwlock2
  2. 使用try_lock方法:使用try_lock方法尝试获取锁,如果获取失败,线程可以选择释放已经获取的锁,并稍后重试。
use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let rwlock1 = Arc::new(RwLock::new(0));
    let rwlock2 = Arc::new(RwLock::new(0));

    let rwlock1_clone = Arc::clone(&rwlock1);
    let rwlock2_clone = Arc::clone(&rwlock2);

    let thread1 = thread::spawn(move || {
        loop {
            match (rwlock1_clone.try_write(), rwlock2_clone.try_write()) {
                (Ok(mut lock1), Ok(mut lock2)) => {
                    println!("Thread 1 acquired both locks");
                    break;
                }
                _ => {
                    println!("Thread 1 failed to acquire locks, retrying...");
                    thread::sleep(std::time::Duration::from_millis(100));
                }
            }
        }
    });

    let thread2 = thread::spawn(move || {
        loop {
            match (rwlock1.try_write(), rwlock2.try_write()) {
                (Ok(mut lock1), Ok(mut lock2)) => {
                    println!("Thread 2 acquired both locks");
                    break;
                }
                _ => {
                    println!("Thread 2 failed to acquire locks, retrying...");
                    thread::sleep(std::time::Duration::from_millis(100));
                }
            }
        }
    });

    thread1.join().unwrap();
    thread2.join().unwrap();
}

在这个改进后的代码中,线程使用try_write尝试获取锁,如果获取失败则等待一段时间后重试,避免了死锁。