面试题答案
一键面试好处
- 避免死锁:在等待条件变量时自动释放锁,使得其他线程有机会获取锁并修改共享状态,从而满足条件变量的条件。如果不自动释放锁,其他线程将无法修改共享状态,导致所有线程永远等待,产生死锁。
- 提高并发效率:在等待期间释放锁,其他线程可以继续执行与共享状态相关的操作,提升系统整体的并发性能。
潜在风险
- 虚假唤醒:
wait
方法可能在没有被notify
或notify_all
明确唤醒的情况下返回,即虚假唤醒。这可能导致线程在条件未满足时就开始执行相关操作,需要在代码中再次检查条件是否满足。 - 竞态条件:虽然自动释放和重新获取锁在大多数情况下是安全的,但在复杂场景下,尤其是在条件检查和操作之间存在时间窗口时,可能会引入竞态条件。例如,一个线程在检查条件满足后,在执行相关操作前,另一个线程可能已经修改了共享状态,导致操作出现错误。
确保公平性
在Rust中,Condvar
默认使用公平唤醒策略。即等待时间最长的线程会优先被唤醒。当使用notify_all
唤醒所有等待线程时,系统会按照线程等待的先后顺序依次唤醒,先等待的线程先被唤醒并获取锁。
实现不公平唤醒
在Rust中没有直接提供标准的不公平唤醒机制。要实现不公平唤醒,可以自己维护一个线程优先级队列(例如使用std::collections::BinaryHeap
),当需要唤醒线程时,从队列中按照自定义的规则(如优先级高的先唤醒)取出线程并手动通知。以下是一个简单示例:
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::collections::BinaryHeap;
// 自定义线程优先级结构体
#[derive(PartialEq, Eq)]
struct ThreadPriority {
priority: i32,
id: usize,
}
impl Ord for ThreadPriority {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// 这里实现最大堆,优先级高的在堆顶
other.priority.cmp(&self.priority)
}
}
impl PartialOrd for ThreadPriority {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
fn main() {
let cond = Arc::new(Condvar::new());
let lock = Arc::new(Mutex::new(()));
let mut priority_queue = BinaryHeap::new();
let mut handles = Vec::new();
for i in 0..5 {
let cond_clone = cond.clone();
let lock_clone = lock.clone();
let mut priority_queue_clone = priority_queue.clone();
let handle = thread::spawn(move || {
let mut data = lock_clone.lock().unwrap();
let priority = i as i32;
let thread_id = i;
priority_queue_clone.push(ThreadPriority { priority, id: thread_id });
data = cond_clone.wait(data).unwrap();
println!("Thread {} with priority {} woke up", thread_id, priority);
});
handles.push(handle);
}
// 模拟唤醒线程
let _ = thread::spawn(move || {
let mut data = lock.lock().unwrap();
let mut priority_queue = priority_queue;
while let Some(priority) = priority_queue.pop() {
println!("Waking up thread {} with priority {}", priority.id, priority.priority);
cond.notify_one();
data = cond.wait(data).unwrap();
}
});
for handle in handles {
handle.join().unwrap();
}
}
在上述代码中,我们使用BinaryHeap
实现了一个简单的优先级队列,按照优先级高低唤醒线程,从而模拟了不公平唤醒。