面试题答案
一键面试并发控制机制设计
- Mutex:用于保护共享资源,确保同一时间只有一个线程可以对其进行可变借用操作。在Rust中,
Mutex
通过lock
方法获取锁,返回一个MutexGuard
,它实现了Deref
和DerefMut
,在作用域结束时自动释放锁。 - RwLock:当读操作远多于写操作时,可以使用
RwLock
。读操作可以并发执行,而写操作需要独占锁。读锁通过read
方法获取,写锁通过write
方法获取。 - 条件变量:用于线程间的同步。当一个线程需要等待某个条件满足时,可以使用条件变量。在Rust中,
Condvar
与Mutex
结合使用。线程先获取Mutex
的锁,然后在条件变量上调用wait
方法,此时锁会被暂时释放,线程进入等待状态。当其他线程满足条件后,通过条件变量的notify_one
或notify_all
方法唤醒等待的线程,被唤醒的线程重新获取锁并继续执行。
死锁问题分析及解决方案
- 死锁分析:死锁通常发生在多个线程互相等待对方释放锁的情况下。例如,线程A持有锁1并等待锁2,而线程B持有锁2并等待锁1。
- 解决方案:
- 避免循环依赖:设计程序时,确保线程获取锁的顺序一致,避免形成循环依赖。
- 使用
try_lock
方法:Mutex
和RwLock
都提供了try_lock
方法,尝试获取锁但不会阻塞。线程可以先尝试获取所有需要的锁,如果获取失败则释放已获取的锁并重新尝试,这样可以避免死锁。
饥饿问题分析及解决方案
- 饥饿分析:饥饿发生在某些线程长时间无法获取到锁,导致其任务无法执行。例如,高优先级线程频繁获取锁,使得低优先级线程长时间等待。
- 解决方案:
- 公平锁:某些实现的锁可以支持公平性,按照线程请求锁的顺序分配锁,避免高优先级线程一直抢占锁。
- 动态优先级调整:根据线程等待时间动态调整线程的优先级,等待时间越长优先级越高,从而确保每个线程都有机会执行。
核心代码框架
use std::sync::{Arc, Mutex, Condvar, RwLock};
use std::thread;
// 共享资源
let shared_resource = Arc::new(RwLock::new(SomeData {}));
let condition_variable = Arc::new(Condvar::new());
let mutex = Arc::new(Mutex::new(()));
// 线程池创建
let mut handles = Vec::new();
for _ in 0..num_threads {
let shared_resource = shared_resource.clone();
let condition_variable = condition_variable.clone();
let mutex = mutex.clone();
let handle = thread::spawn(move || {
// 获取锁
let mut data = shared_resource.write().unwrap();
// 等待条件满足
let _ = condition_variable.wait(mutex.lock().unwrap()).unwrap();
// 对共享资源进行操作
//...
});
handles.push(handle);
}
// 主线程中通知条件满足
let data = shared_resource.write().unwrap();
*data = SomeNewData {};
condition_variable.notify_all();
// 等待所有线程完成
for handle in handles {
handle.join().unwrap();
}
在上述代码框架中:
Arc
用于在多个线程间共享数据。RwLock
保护共享资源SomeData
。Condvar
与Mutex
配合实现线程间的同步,等待某个条件满足后对共享资源进行操作。- 线程池中的线程获取锁并等待条件变量的通知,主线程在适当的时候通知所有等待的线程。