面试题答案
一键面试Mutex
和RwLock
的区别
- 读写特性
Mutex
(互斥锁):一次只允许一个线程访问受保护的数据。无论是读取还是写入操作,都需要获取锁,这意味着同一时间只有一个线程可以进入临界区对数据进行操作。这保证了数据的一致性,但在多线程读取频繁的场景下,性能会受到影响,因为每次读取都要竞争锁。RwLock
(读写锁):允许多个线程同时进行读取操作,因为读取操作不会修改数据,所以多个线程同时读取不会产生数据竞争问题。但是,当有线程要进行写入操作时,需要独占锁,此时其他读写线程都不能访问数据。这在读取操作远多于写入操作的场景下能显著提升性能。
- 适用场景
Mutex
:适用于读写操作频率相近,或者写入操作较为频繁的场景。因为Mutex
简单直接,每次操作都保证了数据的独占访问,能有效防止数据竞争。RwLock
:适用于读取操作远多于写入操作的场景。通过允许并发读取,能提高系统的并发性能。
场景举例
- 使用
Mutex
的场景 假设我们有一个银行账户类,账户余额的修改和查询操作频率相近。每次修改余额(写入操作)或者查询余额(读取操作)都需要保证数据的一致性,防止多个线程同时修改或者读取导致数据错误。
use std::sync::{Arc, Mutex};
struct BankAccount {
balance: i32,
}
fn main() {
let account = Arc::new(Mutex::new(BankAccount { balance: 0 }));
let handle1 = {
let account = account.clone();
std::thread::spawn(move || {
let mut guard = account.lock().unwrap();
guard.balance += 100;
})
};
let handle2 = {
let account = account.clone();
std::thread::spawn(move || {
let guard = account.lock().unwrap();
println!("Balance: {}", guard.balance);
})
};
handle1.join().unwrap();
handle2.join().unwrap();
}
在这个例子中,无论是修改余额(写入)还是查询余额(读取),都使用Mutex
来保证数据一致性,因为读写操作频率相近,Mutex
能很好地满足需求。
- 使用
RwLock
的场景 假设我们有一个配置文件读取系统,配置文件在运行时很少修改,但会被多个线程频繁读取。
use std::sync::{Arc, RwLock};
struct Config {
data: String,
}
fn main() {
let config = Arc::new(RwLock::new(Config {
data: "default config".to_string(),
}));
let mut handles = vec![];
for _ in 0..10 {
let config = config.clone();
let handle = std::thread::spawn(move || {
let guard = config.read().unwrap();
println!("Read config: {}", guard.data);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// 模拟偶尔的写入操作
let mut guard = config.write().unwrap();
guard.data = "new config".to_string();
}
在这个例子中,由于读取操作频繁,写入操作很少,使用RwLock
能提高系统的并发性能,允许多个线程同时读取配置数据。