到目前为止,我一直使用pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)实现这个目的(我猜这就是STL在posix上使用的方式)。它接受一个互斥量,而不是一个锁。
这里有什么区别吗?std::condition_variable使用std::unique_lock是否是一种优化?如果是,它具体是如何更快?
那么没有技术原因吗?
我赞同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
类型传递给它?
答案:
这是非常有用的功能。例如这篇文章演示了在条件变量上以共享模式等待shared_lock
(rwlock)的代码(这在posix世界中是无法想象的,但非常有用)。但是,这个功能更为昂贵。
因此,委员会引入了一个具有此功能的新类型:
`condition_variable_any`
通过使用这个condition_variable
适配器,可以等待任何可锁定类型。如果它有lock()
和unlock()
成员函数,那么就可以使用。一个正确的condition_variable_any
的实现需要有一个condition_variable
数据成员和一个shared_ptr<mutex>
数据成员。
因为这个新功能比基本的condition_variable::wait
更昂贵,并且因为condition_variable
是一个如此底层的工具,所以这个非常有用但更昂贵的功能被放入一个单独的类中,这样只有在使用时才需要付出代价。
lock_guard
,还是condition_variable
,或者可能是condition_variable::wait
? - Howard Hinnantcondition_variable_any
。 - Stephan Dollbergcondition_variable::wait
。是的,condition_variable_any
是正确的选择。而且之所以没有将这个功能整合到 condition_variable
中,是因为它更加消耗资源。而 condition_variable
是一种非常底层的工具,需要尽可能高效。只有在使用 condition_variable_any
的时候才会支付额外的功能费用。 - Howard Hinnantcondition_variable_any
。 - Stephan Dollberg这基本上是一种API设计决策,通过默认设置API尽可能安全(附加开销被视为可以忽略)。通过要求传递unique_lock
而不是原始的mutex
,API的用户被指向编写正确的代码(在异常存在的情况下)。
近年来,C++语言的重点已经转向通过默认设置使其更安全(但仍允许用户尝试努力并自毁)。
std::condition_variable
使用了Linux中的条件变量pthread_cond
。pthread_cond_wait_*
函数需要一个mutex
作为输入变量。只有unique_lock
有成员函数可以获取mutex
。其他的如lock_guard
或scoped_lock
都不符合这个要求。