我正在寻找一个我们所拥有的错误,其中一些凌乱的线程/条件变量类正在更新以使用C ++ 11线程。在寻找过程中,我在GCC代码库中遇到了以下内容:
template<typename _Lock>
void
wait(_Lock& __lock)
{
unique_lock<mutex> __my_lock(_M_mutex);
_Unlock<_Lock> __unlock(__lock);
// _M_mutex must be unlocked before re-locking __lock so move
// ownership of _M_mutex lock to an object with shorter lifetime.
unique_lock<mutex> __my_lock2(std::move(__my_lock));
_M_cond.wait(__my_lock2);
}
尽管有评论,但我很难理解移动构造函数的目的是__my_lock2。为什么__my_lock在这里移动到__my_lock2?
答案 0 :(得分:10)
这看起来不像condition_variable
代码。它看起来像condition_variable_any
代码。后者具有模板化等待功能。前者没有。
N2406可以阐明你所看到的。在N2406中,condition_variable_any
被命名为gen_cond_var
,但除此之外,类型是相同的。本文描述了一种死锁情况,除非特别注意确保_M_mutex
和__lock
以非常特定的顺序锁定和解锁,否则会实现死锁。
虽然您显示的代码和N2406上的代码不一样,但我强烈怀疑它们都是为了锁定和解锁这两个锁定而构建的,以避免N2406中描述的死锁。如果不进一步了解_Unlock
的定义,我不能绝对肯定。然而,一个强有力的线索是N2406中这个等待函数的最终评论:
} // mut_.unlock(), external.lock()
即。在锁定外部__lock
之前,必须解锁成员互斥锁。通过将__my_lock
移动到范围小于__unlock
的局部变量,很可能正确排序
} // _M_mutex.unlock(), __lock.lock()
已经实现。要理解为什么这个排序非常重要,你必须阅读N2406的相关部分(它可以防止死锁)。
答案 1 :(得分:3)
该函数(我认为是std::condition_variable_any<_Lock>::wait
)通过建立锁定顺序不变来避免死锁,__lock
必须在获取_M_mutex
之前锁定。在这样做时,它必须以某种方式保证在等待内部条件变量__lock
时不保持_M_cond
。请注意,_Unlock<_Lock>
是RAII 解锁对象。它通过构造函数中的解锁和析构函数中的锁定执行通常锁定保护的相反功能。事件的必要顺序是:
__lock
(wait
来电的前提条件)_M_mutex
(在__my_lock
构造函数中)__lock
(在__unlock
构造函数中)_M_mutex
并等待_M_cond
(发生在_M_cond.wait
)_M_mutex
(也在_M_cond.wait
)_M_mutex
(在__my_lock2
析构函数中)__lock
(在__unlock
析构函数中)从__my_lock
迁移到__my_lock2
是必要的,以便在__my_lock2
之前销毁__unlock
,以确保事件6发生在7之前而不是之后。
答案 2 :(得分:1)
看起来重新排序__lock
和__my_lock
变量的销毁。
调用应如下所示:
construct __my_lock // locks _M_mutex
construct __unlock // unlocks __lock
construct __my_lock2 // Does nothing as its a move.
_M_cond.wait(__my_lock2);
destroy __mylock2 // unlocks __M_mutex
destroy __unlock // locks __lock again
destroy __mylock // does nothing as its been moved
如果没有移动,订单将是
construct __my_lock // locks _M_mutex
construct __unlock // unlocks __lock
_M_cond.wait(__my_lock);
destroy __unlock // locks __lock
destroy __mylock // unlocks _M_mutex
这会导致其他答案中提到的死锁