1.65、使用条件变量的时候需要注意什么?

条件变量本身无“记忆”功能,可能发生“丢失唤醒”问题

如果调用signal(或notify_one)时,没有线程处于等待状态,那么该信号会丢失,后续的wait不会捕获到这个信号,导致线程可能永远阻塞。因此,必须保证等待线程先进入等待状态,或者通过条件判断避免遗漏唤醒。

必须在持有互斥锁的情况下检查条件并调用wait,保证操作的原子性

条件的判断和等待必须在同一把锁保护下完成,防止竞态条件。通常使用std::unique_lock<std::mutex>,并结合cv.wait(lock, predicate)的形式,内部会自动释放和重新获取锁,同时避免虚假唤醒带来的错误。

修改条件变量关联的共享状态时,也必须持锁操作

即使共享变量是原子类型,也应在持锁状态下修改,以保证修改对等待线程的正确可见性,避免同步问题。

防止虚假唤醒(spurious wakeup)

条件变量等待可能被非预期唤醒,因此必须在wait返回后重新检查条件(使用带谓词的wait重载自动完成此操作),确保线程只有在条件满足时才继续执行。

通知操作(notify_onenotify_all)时不必持锁,但修改条件状态时必须持锁

通知线程在修改完共享条件后调用notify,通知等待线程重新检查条件。通知时不强制要求持锁,但为了保证状态修改的同步,修改状态时必须持锁。

总结来说,正确使用条件变量的最佳实践是:

  • • 在持锁状态下判断条件,若条件不满足则调用wait,等待时自动释放锁,唤醒后重新加锁并检查条件。
  • • 修改条件状态时必须持锁,并在修改后调用notify_onenotify_all通知等待线程。
  • • 理解条件变量无记忆特性,避免信号丢失,确保等待线程先于通知线程进入等待状态或通过条件判断规避。
  • • 始终使用带谓词的wait接口防止虚假唤醒导致的错误执行。
    本文首发于【讳疾忌医-note】公众号,未经授权,不得转载。
    (加入我的知识星球,免费获取账号,解锁所有文章。)
阅读剩余
THE END