面试题答案
一键面试策略阐述
- 死锁预防:死锁通常发生在多个线程以不同顺序获取锁的情况下。为了预防死锁,我们应定义一个全局的锁获取顺序。所有线程都必须按照这个顺序来获取锁,这样就不会形成循环依赖,从而避免死锁。
- 性能优化:虽然严格的顺序可以预防死锁,但可能会导致性能问题。例如,如果线程频繁需要获取多个锁,按照固定顺序获取可能会造成不必要的等待。为了优化性能,可以考虑以下几点:
- 锁粒度控制:尽量减小锁保护资源的粒度,使锁的持有时间尽可能短。
- 并行获取:对于没有依赖关系的锁,可以考虑并行获取,提高并发性能。
代码示例
use std::sync::{Arc, Mutex};
use std::thread;
// 定义两个Mutex保护的资源
let resource_a = Arc::new(Mutex::new(0));
let resource_b = Arc::new(Mutex::new(0));
// 克隆Arc指针以便传递给线程
let resource_a_clone = resource_a.clone();
let resource_b_clone = resource_b.clone();
// 启动第一个线程
let thread1 = thread::spawn(move || {
// 按照全局顺序获取锁
let mut a = resource_a_clone.lock().unwrap();
let mut b = resource_b_clone.lock().unwrap();
// 使用资源a和b
*a += 1;
*b += 1;
});
// 启动第二个线程
let thread2 = thread::spawn(move || {
// 同样按照全局顺序获取锁
let mut a = resource_a.lock().unwrap();
let mut b = resource_b.lock().unwrap();
// 使用资源a和b
*a += 2;
*b += 2;
});
// 等待线程结束
thread1.join().unwrap();
thread2.join().unwrap();
在这个示例中,两个线程都按照resource_a
-> resource_b
的顺序获取锁,预防了死锁。同时,通过合理安排锁的获取时机和锁粒度,在一定程度上保证了性能。如果resource_a
和resource_b
之间没有依赖关系,可以考虑使用std::sync::TryLockError
来尝试并行获取锁,进一步优化性能:
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::TryLockError;
// 定义两个Mutex保护的资源
let resource_a = Arc::new(Mutex::new(0));
let resource_b = Arc::new(Mutex::new(0));
// 克隆Arc指针以便传递给线程
let resource_a_clone = resource_a.clone();
let resource_b_clone = resource_b.clone();
// 启动第一个线程
let thread1 = thread::spawn(move || {
let result = (resource_a_clone.try_lock(), resource_b_clone.try_lock());
match result {
(Ok(mut a), Ok(mut b)) => {
// 使用资源a和b
*a += 1;
*b += 1;
},
_ => {
// 获取锁失败,处理失败逻辑
}
}
});
// 启动第二个线程
let thread2 = thread::spawn(move || {
let result = (resource_a.try_lock(), resource_b.try_lock());
match result {
(Ok(mut a), Ok(mut b)) => {
// 使用资源a和b
*a += 2;
*b += 2;
},
_ => {
// 获取锁失败,处理失败逻辑
}
}
});
// 等待线程结束
thread1.join().unwrap();
thread2.join().unwrap();
这种方式允许线程尝试并行获取锁,如果获取成功则可以并行处理,提高性能。如果获取失败,则可以根据具体需求处理失败逻辑,例如重试或执行其他任务。