MST

星途 面试题库

面试题:Rust中Mutex和RwLock在并发连接处理场景中的应用及选择

在处理并发连接时,常常需要保护共享资源。请详细说明Rust中的Mutex(互斥锁)和RwLock(读写锁)的工作原理,在什么情况下应该选择使用Mutex,什么情况下选择RwLock?结合一个并发连接处理的实际场景代码,分析使用这两种锁的优势和可能出现的问题。
25.3万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

Rust 中 Mutex 和 RwLock 工作原理

Mutex(互斥锁)

Mutex 是“mutual exclusion”的缩写,即互斥。其工作原理是在同一时间只允许一个线程访问共享资源。当一个线程想要访问共享资源时,它必须先获取 Mutex 的锁。如果锁当前可用,线程将获得锁并可以访问资源;如果锁已被其他线程持有,当前线程会被阻塞,直到持有锁的线程释放锁。

RwLock(读写锁)

RwLock 允许多个线程同时进行读操作,因为读操作不会修改共享资源,所以不会产生数据竞争。但是,当有线程想要进行写操作时,必须独占锁,以防止其他读或写操作同时进行,避免数据不一致。也就是说,同一时间要么有多个读操作,要么只有一个写操作。

选择使用场景

选择 Mutex 的场景

当共享资源需要频繁地进行读写操作,且读写操作都可能修改资源状态时,应使用 Mutex。例如,一个银行账户的余额,无论是存款(写操作)还是查询(读操作)都需要确保数据的一致性,这种情况下 Mutex 可以防止同时的读写操作导致数据错误。

选择 RwLock 的场景

当读操作远远多于写操作时,RwLock 更合适。比如一个缓存系统,大量线程可能会读取缓存数据,但只有在缓存过期等少数情况下才会更新缓存,此时 RwLock 可以提高并发性能,因为多个读操作可以并行执行。

实际场景代码与分析

使用 Mutex 的示例代码

use std::sync::{Arc, Mutex};
use std::thread;

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

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

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

    let result = data.lock().unwrap();
    println!("Final value: {}", *result);
}

优势:能确保数据的一致性,在任何时刻只有一个线程可以修改共享数据,避免数据竞争。 可能问题:由于同一时间只有一个线程能访问资源,如果有大量线程竞争锁,可能会导致性能瓶颈,特别是在高并发场景下,线程等待锁的时间会增加。

使用 RwLock 的示例代码

use std::sync::{Arc, RwLock};
use std::thread;

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

    for _ in 0..5 {
        let data = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let num = data.read().unwrap();
            println!("Read value: {}", *num);
        });
        handles.push(handle);
    }

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

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

    let result = data.read().unwrap();
    println!("Final value: {}", *result);
}

优势:读操作可以并行执行,提高了读多写少场景下的并发性能。对于读操作频繁的共享资源,能显著减少线程等待时间。 可能问题:如果写操作频繁,会导致读操作被阻塞,因为写操作需要独占锁。而且如果有线程长时间持有写锁,可能会造成读线程饥饿现象。