何时应在调用notify_one()前手动解锁mutex?
在cppreference的std::condition_variable示例中,会在调用cv.notify_one()前手动解锁mutex:
// Manual unlocking is done before notifying, to avoid waking up // the waiting thread only to block again (see notify_one for details) lk.unlock(); cv.notify_one();
对应的cppreference中notify_one的注释如下:
通知线程无需持有与等待线程相同的mutex锁;实际上这样做是一种性能劣化,因为被通知的线程会立即再次阻塞,等待通知线程释放锁。不过,某些实现(尤其是许多pthreads实现)会识别这种情况,通过在notify调用中将等待线程从条件变量的队列直接转移到mutex的队列,而不唤醒它,从而避免这种“忙等”场景。
当需要精确调度事件时,可能仍有必要在持有锁的情况下进行通知,例如,如果等待线程在条件满足时会退出程序,导致通知线程的condition_variable被销毁。在解锁mutex后但调用notify前发生的虚假唤醒会导致notify被调用在已销毁的对象上。
关于两种做法的优劣,Stack Overflow上有回答指出提前解锁至少不会更差,但也有开发者(如@MaximEgorushkin和@DavidSchwartz)认为先通知再解锁更好。
这并非单纯的个人偏好,而是取决于具体场景:
何时该选择解锁后再通知?
这是通用的优先方案,能避免被唤醒的线程刚恢复就因锁被占用而再次阻塞的性能损耗。即便部分平台的实现有优化机制可以规避这种问题,解锁后通知的方式也不会带来额外风险,兼容性和普适性更强。
何时必须持有锁通知?
当涉及到对象生命周期的严格控制时,比如等待线程满足条件后会直接销毁当前的condition_variable,这时必须在持有锁的状态下执行通知。因为如果先解锁再通知,中间的时间窗口里,等待线程可能因虚假唤醒提前销毁对象,导致后续的notify_one()调用访问已销毁的内存,触发未定义行为。
内容的提问来源于stack exchange,提问作者starriet 차주녕

