面试题答案
一键面试架构设计
- 数据层:
- 使用
RwLock
来保护共享数据结构。例如,如果有一个共享的数据库缓存,多个读操作可以同时进行,但写操作需要独占访问。 - 示例代码:
use std::sync::{Arc, RwLock}; struct Data { value: i32 } let shared_data = Arc::new(RwLock::new(Data { value: 0 }));
- 使用
- 生产者 - 消费者模型:
- 使用
Channel
来传递数据。生产者将数据发送到通道,消费者从通道接收数据进行处理。这有助于解耦不同部分的系统,并且可以实现异步处理。 - 示例代码:
use std::sync::mpsc; let (tx, rx) = mpsc::channel(); std::thread::spawn(move || { tx.send(42).unwrap(); }); let received = rx.recv().unwrap();
- 使用
- 任务处理层:
- 对于需要独占访问的资源或操作,使用
Mutex
。例如,当更新一些全局状态或者执行一些不允许并发执行的特定逻辑时。 - 示例代码:
use std::sync::Mutex; let counter = Arc::new(Mutex::new(0)); let c = counter.clone(); std::thread::spawn(move || { let mut num = c.lock().unwrap(); *num += 1; });
- 对于需要独占访问的资源或操作,使用
并发原语协调
- 读 - 写协调:
- 读操作时,使用
RwLock
的read
方法获取读锁。多个读操作可以并行进行,不会相互阻塞。 - 写操作时,使用
RwLock
的write
方法获取写锁。写锁会阻塞所有读操作和其他写操作,以保证数据一致性。 - 例如:
let shared_data = Arc::new(RwLock::new(Data { value: 0 })); let data_read = shared_data.read().unwrap(); let mut data_write = shared_data.write().unwrap();
- 读操作时,使用
- 生产者 - 消费者与共享数据协调:
- 生产者将数据发送到通道,消费者从通道接收数据后,可能需要访问共享数据。此时,消费者在访问共享数据前,要根据需求获取
RwLock
的读锁或写锁。 - 例如,消费者可能根据接收到的数据更新共享缓存:
let shared_cache = Arc::new(RwLock::new(Cache::new())); let (tx, rx) = mpsc::channel(); std::thread::spawn(move || { for data in rx { let mut cache = shared_cache.write().unwrap(); cache.update(data); } });
- 生产者将数据发送到通道,消费者从通道接收数据后,可能需要访问共享数据。此时,消费者在访问共享数据前,要根据需求获取
- Mutex与其他原语协调:
- 当需要对一些操作进行独占控制时,在获取
RwLock
锁或使用通道操作之前,先获取Mutex
锁。例如,在更新全局配置同时需要更新共享数据,就先获取Mutex
锁保证配置更新的原子性,再获取RwLock
的写锁更新共享数据。
- 当需要对一些操作进行独占控制时,在获取
死锁分析及预防策略
- 死锁情况分析:
- 循环依赖:例如,线程A持有
Mutex
A并等待RwLock
B的写锁,而线程B持有RwLock
B的读锁并等待Mutex
A的释放,这就形成了死锁。 - 嵌套锁获取顺序不一致:如果不同线程以不同顺序获取多个锁,可能导致死锁。比如,线程1先获取
RwLock
A的写锁,再获取Mutex
B,而线程2先获取Mutex
B,再获取RwLock
A的写锁。
- 循环依赖:例如,线程A持有
- 预防策略:
- 固定锁获取顺序:在整个系统中,规定获取锁的顺序。例如,总是先获取
Mutex
,再获取RwLock
的写锁。 - 使用
try_lock
方法:对于Mutex
和RwLock
,可以使用try_lock
方法尝试获取锁。如果获取失败,线程可以选择重试或者执行其他操作,避免无限等待。 - 死锁检测工具:使用如
deadlock
crate 等死锁检测工具,在开发和测试阶段运行程序,检测潜在的死锁情况。
- 固定锁获取顺序:在整个系统中,规定获取锁的顺序。例如,总是先获取