MST
星途 面试题库

面试题:Rust中Mutex与RwLock在并发场景下的应用对比

在Rust并发编程里,Mutex和RwLock都是用于线程同步的机制。请详细阐述Mutex和RwLock的工作原理,在什么并发场景下应该优先选择Mutex,什么场景下优先选择RwLock,并举例说明如何在实际代码中正确使用它们以避免死锁等问题。
35.1万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

Mutex工作原理

Mutex(互斥锁)是一种最基本的同步原语,它基于“互斥访问”的原则。当一个线程获取了Mutex的锁,其他线程就必须等待,直到该线程释放锁。这确保了同一时间只有一个线程可以访问被Mutex保护的数据,从而避免数据竞争。

RwLock工作原理

RwLock(读写锁)允许多个线程同时进行读操作,因为读操作不会修改数据,所以不会产生数据竞争。但是,当有一个线程进行写操作时,其他读线程和写线程都必须等待,直到写操作完成并释放锁。这样可以保证在写操作时数据的一致性。

优先选择Mutex的场景

  1. 频繁写操作场景:如果在并发环境中,对共享数据的写操作远远多于读操作,那么使用Mutex更合适。因为RwLock在写操作时会阻塞所有读操作,而Mutex直接限制同一时间只有一个线程能访问数据,无论读写。
  2. 数据一致性要求高且操作复杂场景:当共享数据的操作涉及到复杂的逻辑,需要保证严格的数据一致性,使用Mutex可以简化逻辑,因为同一时间只有一个线程能操作数据。

优先选择RwLock的场景

  1. 多读少写场景:当对共享数据的读操作远远多于写操作时,RwLock能显著提高并发性能。因为多个读线程可以同时访问数据,而只有写操作时才会阻塞其他线程。

实际代码中使用Mutex以避免死锁

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

fn main() {
    let data = Arc::new(Mutex::new(0));
    let data_clone = data.clone();

    let handle = thread::spawn(move || {
        let mut num = data_clone.lock().unwrap();
        *num += 1;
    });

    let mut num = data.lock().unwrap();
    *num += 1;

    handle.join().unwrap();

    println!("Final value: {}", *num);
}

在这段代码中,通过ArcMutex来保护共享数据data。每个线程在访问数据前先获取锁lock(),操作完成后自动释放锁(MutexGuard离开作用域时),避免死锁。

实际代码中使用RwLock以避免死锁

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

fn main() {
    let data = Arc::new(RwLock::new(0));
    let data_clone = data.clone();

    let read_handle = thread::spawn(move || {
        let num = data_clone.read().unwrap();
        println!("Read value: {}", *num);
    });

    let write_handle = thread::spawn(move || {
        let mut num = data.write().unwrap();
        *num += 1;
    });

    read_handle.join().unwrap();
    write_handle.join().unwrap();

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

这里使用RwLock来保护共享数据data。读线程通过read()获取读锁,写线程通过write()获取写锁。获取锁后同样会在MutexGuard离开作用域时自动释放锁,避免死锁。