面试题答案
一键面试可能遇到的问题
- 数据竞争:多个线程同时读取和修改
SharedData
时,可能会导致数据竞争。例如,一个线程正在读取data
或metadata
,另一个线程同时修改它们,这会导致未定义行为。 - 引用失效:当一个线程持有
&SharedData
引用时,如果共享数据在其他线程中被释放或重新分配内存,该引用可能会失效,导致悬垂指针问题。
解决方案及优缺点
1. 使用 Mutex
代码示例:
use std::sync::{Mutex, Arc};
use std::collections::HashMap;
struct SharedData {
data: Vec<i32>,
metadata: HashMap<String, String>,
}
fn main() {
let shared = Arc::new(Mutex::new(SharedData {
data: vec![1, 2, 3],
metadata: HashMap::from([("key".to_string(), "value".to_string())]),
}));
let handles = (0..3).map(|_| {
let shared_clone = shared.clone();
std::thread::spawn(move || {
let lock = shared_clone.lock().unwrap();
// 在这里进行比较操作
println!("Data: {:?}, Metadata: {:?}", lock.data, lock.metadata);
})
}).collect::<Vec<_>>();
for handle in handles {
handle.join().unwrap();
}
}
优点:
- 简单直观,Rust 的
Mutex
实现安全,能有效防止数据竞争。 - 可以控制对共享数据的访问,同一时间只有一个线程能获取锁并访问数据。 缺点:
- 性能瓶颈,锁的竞争可能导致线程阻塞,影响程序整体性能,尤其是在高并发场景下。
- 死锁风险,如果多个线程以不同顺序获取多个锁,可能会导致死锁。
2. 使用 RwLock
代码示例:
use std::sync::{RwLock, Arc};
use std::collections::HashMap;
struct SharedData {
data: Vec<i32>,
metadata: HashMap<String, String>,
}
fn main() {
let shared = Arc::new(RwLock::new(SharedData {
data: vec![1, 2, 3],
metadata: HashMap::from([("key".to_string(), "value".to_string())]),
}));
let read_handles = (0..3).map(|_| {
let shared_clone = shared.clone();
std::thread::spawn(move || {
let lock = shared_clone.read().unwrap();
// 在这里进行比较操作
println!("Data: {:?}, Metadata: {:?}", lock.data, lock.metadata);
})
}).collect::<Vec<_>>();
let write_handle = std::thread::spawn(move || {
let mut lock = shared.write().unwrap();
lock.data.push(4);
lock.metadata.insert("new_key".to_string(), "new_value".to_string());
});
for handle in read_handles {
handle.join().unwrap();
}
write_handle.join().unwrap();
}
优点:
- 适合读多写少的场景,读操作可以并发执行,提高了并发性能。
- 依然能有效防止数据竞争。 缺点:
- 实现相对复杂,需要区分读锁和写锁的获取。
- 写操作时会独占锁,可能导致读操作长时间等待,尤其是写操作频繁时。
- 同样存在死锁风险,例如多个线程对读锁和写锁的获取顺序不当。