面试题答案
一键面试设计思路
- 理解借用规则基础:
- Rust 的借用规则确保内存安全和数据一致性。在并发场景下,这些规则能防止数据竞争。
- 例如,同一时间只能有一个可变引用(
&mut
),或者多个不可变引用(&
)。这避免了同时读写共享数据导致的数据不一致。
- 选择合适的数据结构和并发原语:
- Arc 与 Mutex 组合:对于需要在多个线程间共享可变数据,使用
std::sync::Arc
(原子引用计数指针)和std::sync::Mutex
(互斥锁)。Arc
允许数据在多个线程间共享,Mutex
提供了对数据的独占访问,确保同一时间只有一个线程能修改数据。 - RwLock:当读操作远多于写操作时,
std::sync::RwLock
更合适。它允许多个线程同时进行读操作,但写操作时会独占锁,这样在高读场景下能提高系统效率。
- Arc 与 Mutex 组合:对于需要在多个线程间共享可变数据,使用
- 任务划分与数据隔离:
- 将不同功能的任务划分开,尽量减少共享数据的范围。例如,有些任务只负责读,有些任务只负责写,这样可以减少锁的竞争。
- 对于必须共享的数据,通过合理的数据结构设计,将不同类型的数据分开,减少锁的粒度。比如,将经常读和经常写的数据分开存储在不同的结构中。
- 错误处理:
- 在获取锁(如
Mutex
或RwLock
)时,要处理可能出现的错误。例如,Mutex
的lock
方法返回Result
,如果其他线程已经死锁,获取锁可能失败,需要合适的错误处理机制。
- 在获取锁(如
关键代码实现
- 使用 Arc 和 Mutex:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let shared_data = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let data = Arc::clone(&shared_data);
let handle = thread::spawn(move || {
let mut num = data.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final value: {}", *shared_data.lock().unwrap());
}
在这段代码中,Arc<Mutex<i32>>
用于在多个线程间共享 i32
类型的数据。每个线程通过 lock
方法获取锁,修改数据后释放锁。
2. 使用 RwLock:
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let shared_data = Arc::new(RwLock::new(0));
let mut handles = vec![];
for _ in 0..5 {
let data = Arc::clone(&shared_data);
let handle = thread::spawn(move || {
let num = data.read().unwrap();
println!("Read value: {}", num);
});
handles.push(handle);
}
let data = Arc::clone(&shared_data);
let write_handle = thread::spawn(move || {
let mut num = data.write().unwrap();
*num += 1;
});
handles.push(write_handle);
for handle in handles {
handle.join().unwrap();
}
println!("Final value: {}", *shared_data.read().unwrap());
}
这里使用 Arc<RwLock<i32>>
,多个读线程可以同时获取读锁进行读操作,而写线程获取写锁时,其他读线程和写线程都要等待,从而保证数据一致性。同时,由于读操作可以并发进行,提高了读多写少场景下的效率。