在使用std::unique_lock
时,我对std::condition_variable
的作用感到有些困惑。据我了解documentation,std::unique_lock
基本上是一个膨胀的锁定保护,可以在两个锁之间交换状态。
到目前为止,我已经将pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
用于此目的(我猜这是STL在posix上使用的)。它需要一个互斥锁,而不是一个锁。
这有什么区别? std::condition_variable
处理std::unique_lock
优化的事实是什么?如果是这样,它究竟有多快?
答案 0 :(得分:98)
所以没有技术原因?
我赞成了cmeerw的回答,因为我相信他给出了技术上的理由。让我们来看看吧。让我们假装委员会决定让condition_variable
等待mutex
。以下是使用该设计的代码:
void foo()
{
mut.lock();
// mut locked by this thread here
while (not_ready)
cv.wait(mut);
// mut locked by this thread here
mut.unlock();
}
这正是一个不应该使用condition_variable
的方式。在标有以下内容的地区:
// mut locked by this thread here
存在异常安全问题,这是一个严重问题。如果在这些区域中抛出异常(或由cv.wait
本身抛出),则互斥锁的锁定状态将被泄露,除非还在某处放置了try / catch以捕获异常并将其解锁。但这只是你要求程序员编写的更多代码。
假设程序员知道如何编写异常安全代码,并且知道使用unique_lock
来实现它。现在代码看起来像这样:
void foo()
{
unique_lock<mutex> lk(mut);
// mut locked by this thread here
while (not_ready)
cv.wait(*lk.mutex());
// mut locked by this thread here
}
这要好得多,但仍然不是很好。 condition_variable
界面正在让程序员竭尽全力让事情发挥作用。如果lk
意外未引用互斥锁,则可能存在空指针解除引用。并且condition_variable::wait
无法检查此线程是否拥有mut
上的锁。
哦,记住,程序员也可能选择错误的unique_lock
成员函数来暴露互斥锁。 *lk.release()
在这里会是灾难性的。
现在让我们看一下代码是如何使用实际的condition_variable
API编写的,该API采用unique_lock<mutex>
:
void foo()
{
unique_lock<mutex> lk(mut);
// mut locked by this thread here
while (not_ready)
cv.wait(lk);
// mut locked by this thread here
}
wait
函数可以检查lk.owns_lock()
,如果是false
则抛出异常。这些是推动condition_variable
的API设计的技术原因。
此外,condition_variable::wait
不会使用lock_guard<mutex>
,因为lock_guard<mutex>
就是这样说的:我拥有此互斥锁的锁定,直到lock_guard<mutex>
破坏。但是当你调用condition_variable::wait
时,你隐式释放互斥锁上的锁。因此该操作与lock_guard
用例/语句不一致。
我们还需要unique_lock
,以便可以从函数返回锁,将它们放入容器,以异常安全的方式锁定/解锁非作用域模式的互斥锁,因此unique_lock
是自然的选择condition_variable::wait
。
<强>更新强>
bamboon在下面的评论中建议我对比condition_variable_any
,所以这里是:
问题:为什么不condition_variable::wait
模板,以便我可以将任何Lockable
类型传递给它?
<强>答案:强>
这是非常酷的功能。例如,this paper演示了在条件变量上以共享模式等待shared_lock
(rwlock)的代码(在posix世界中闻所未闻,但仍然非常有用)。但功能更昂贵。
因此委员会引入了一种具有此功能的新类型:
`condition_variable_any`
使用此condition_variable
适配器可以等待任何可锁定类型。如果它有成员lock()
和unlock()
,那么您很高兴。正确实施condition_variable_any
需要condition_variable
数据成员和shared_ptr<mutex>
数据成员。
因为这个新功能比基本condition_variable::wait
更昂贵,并且因为condition_variable
是一个低级别的工具,所以这个非常有用但更昂贵的功能被放入一个单独的类中,以便您只如果你使用它就付钱。
答案 1 :(得分:34)
它本质上是一个API设计决策,默认情况下使API尽可能安全(额外开销被视为可忽略不计)。通过要求传递API的unique_lock
而不是原始mutex
用户,可以编写正确的代码(存在例外情况)。
近年来,C ++语言的重点已经转移到默认情况下使其安全(但仍然允许用户在他们想要并且努力尝试时自己动手)。