C ++ 11:为什么std :: condition_variable使用std :: unique_lock?


74

std::unique_lock与一起工作时的角色使我有些困惑std::condition_variable。据我了解的文档std::unique_lock它基本上是一个ated肿的锁卫士,可以在两个锁之间交换状态。

到目前为止,我已经pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)为此目的使用过(我猜这就是STL在posix上使用的功能)。它需要一个互斥锁,而不是锁。

这有什么区别?是std::condition_variable处理std::unique_lock优化的事实吗?如果是这样,它到底有多快?


5
您是否对为什么需要带有条件变量的锁/互斥体,或者锁与互斥锁之间的区别,或者为什么条件变量使用唯一锁而不是互斥体感到困惑?
布雷迪

1
“为什么条件变量使用唯一锁而不是互斥锁”这一点。
lucas clemente 2012年

Answers:


104

所以没有技术原因吗?

我支持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_variableAPI编写代码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
}
  1. 这段代码很简单。
  2. 这是异常安全的。
  3. wait函数可以检查lk.owns_lock()并引发异常false

这些都是推动的API设计的技术原因condition_variable

此外,您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
霍华德·辛南特

对不起,算了。我完全忘了condition_variable_any
Stephan Dollberg,2012年

<nod>啊,模板condition_variable::wait。是的,condition_variable_any要走的路。而功能未归类的原因condition_variable是它更昂贵。而condition_variable就是这样一个低水平的工具,它需要的东西是尽可能高效。如果您使用,则只需为添加的功能付费condition_variable_any
霍华德·辛南特

好的,谢谢您的额外信息。也许将其添加到您的答案中,因为可能会误condition_variable_any用普通的互斥锁。
斯蒂芬·多尔伯格

5
哇,你的回答让我震惊。非常感谢您提供如此详细的信息!
lucas clemente 2012年

35

从本质上讲,这是API设计的决定,默认情况下,使API尽可能安全(额外的开销可以忽略不计)。通过要求传递unique_lock而不是原始mutex值,API的用户被定向为编写正确的代码(在存在异常的情况下)。

近年来,C ++语言的重点已转移到默认情况下使其安全(但仍然允许用户将自己踢到自己的脚上,并尽力而为)。


所以没有技术原因吗?
lucas clemente 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.