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