面试题答案
一键面试-
设计思路:
- 确定资源依赖关系:首先要明确共享资源之间的依赖关系,比如可以使用图(如有向无环图DAG)来表示资源之间的依赖。例如,如果资源B依赖于资源A,那么对B的操作必须在对A的操作完成之后。
- 使用锁来保护资源:对于每个共享资源,使用锁(如
Mutex
或RwLock
)来确保同一时间只有一个线程可以访问该资源。但单纯使用锁可能会导致死锁,所以需要结合依赖关系来合理安排锁的获取顺序。 - 顺序一致性:利用Rust的原子类型(如
AtomicUsize
等)来实现顺序一致性。原子类型提供了SeqCst
内存序,通过原子操作来标记资源的操作顺序。例如,在对资源A操作完成后,使用原子操作更新一个表示A操作完成的标志,在对依赖A的资源B操作前,先检查这个标志。 - 死锁避免:为了避免死锁,所有线程获取锁的顺序必须一致。可以为每个资源分配一个唯一的标识符,线程按照标识符的顺序获取锁。
-
关键Rust特性:
- Mutex和RwLock:
Mutex
(互斥锁)用于保护共享资源,通过lock
方法获取锁,访问完资源后释放锁。例如:
- Mutex和RwLock:
use std::sync::{Mutex, Arc};
let data = Arc::new(Mutex::new(0));
let data_clone = data.clone();
std::thread::spawn(move || {
let mut data = data_clone.lock().unwrap();
*data += 1;
});
- `RwLock`(读写锁)适用于读多写少的场景,允许多个线程同时读,但写操作需要独占锁。读操作使用`read`方法,写操作使用`write`方法。
- 原子类型:
- 例如
AtomicUsize
,通过store
和load
方法,并指定Ordering::SeqCst
内存序来实现顺序一致性。
- 例如
use std::sync::atomic::{AtomicUsize, Ordering};
let flag = AtomicUsize::new(0);
// 操作完成后更新标志
flag.store(1, Ordering::SeqCst);
// 操作前检查标志
if flag.load(Ordering::SeqCst) == 1 {
// 进行操作
}
- 线程安全的数据结构:如
Arc
(原子引用计数)用于在多个线程间安全地共享数据。结合Mutex
或RwLock
,可以实现线程安全的共享资源访问。 - 条件变量:
Condvar
可用于线程间的同步。例如,当某个资源的依赖条件满足时,通过条件变量通知等待的线程。
use std::sync::{Mutex, Condvar};
let data = Mutex::new(0);
let cvar = Condvar::new();
let data_clone = data.clone();
std::thread::spawn(move || {
let mut data = data_clone.lock().unwrap();
while *data < 10 {
data = cvar.wait(data).unwrap();
}
});
// 满足条件时通知
let mut data = data.lock().unwrap();
*data = 10;
cvar.notify_one();