面试题答案
一键面试避免条件变量的虚假唤醒
在高并发场景下,虚假唤醒是指线程在没有满足预期条件的情况下被唤醒。为避免虚假唤醒,应在条件变量等待的循环中检查条件,而不是只检查一次。例如在Go语言中:
cond.L.Lock()
for!condition {
cond.Wait()
}
// 执行满足条件后的操作
cond.L.Unlock()
在其他语言如C++中也类似:
std::unique_lock<std::mutex> lock(mutex);
while (!condition) {
cond.wait(lock);
}
// 执行满足条件后的操作
这样即使发生虚假唤醒,线程会再次检查条件,若条件不满足则继续等待。
优化性能,减少锁竞争
- 减少锁的粒度:只在必要时锁定临界区。例如将大的临界区拆分成多个小的临界区,尽量缩短持有锁的时间。
- 使用读写锁:如果读操作远多于写操作,可以使用读写锁。读操作时多个协程可以同时获取读锁,写操作时获取写锁,这样可以减少锁竞争。
- 无锁数据结构:对于一些场景,可以使用无锁数据结构,如无锁队列、无锁哈希表等,从根本上避免锁竞争。但无锁数据结构实现复杂,且需要特定的硬件支持。
Broadcast
和Signal
方法的使用选择及原因
Broadcast
:- 使用场景:当所有等待条件变量的协程都需要被唤醒去检查条件时,使用
Broadcast
。例如在一个资源池被重置,所有等待获取资源的协程都需要重新检查资源是否可用的场景。 - 原因:它会唤醒所有等待在条件变量上的协程,这些协程被唤醒后会竞争锁,然后检查条件。如果只有部分协程需要被唤醒,可能会造成不必要的唤醒和锁竞争。
- 使用场景:当所有等待条件变量的协程都需要被唤醒去检查条件时,使用
Signal
:- 使用场景:当只有一个等待条件变量的协程需要被唤醒时,使用
Signal
。例如在一个生产者 - 消费者模型中,生产者生产了一个数据,只需要唤醒一个消费者协程来处理这个数据。 - 原因:它只会唤醒一个等待在条件变量上的协程,相比
Broadcast
减少了不必要的唤醒和锁竞争,能提高性能。
- 使用场景:当只有一个等待条件变量的协程需要被唤醒时,使用