79

我对std::unique_lock使用std::condition_variable. 据我了解的文档std::unique_lock基本上是一个臃肿的锁守卫,可以在两个锁之间交换状态。

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

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

4

2 回答 2

109

所以没有技术原因吗?

我赞成 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.

更新

竹子在下面的评论中建议我对比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>数据成员。

因为这个新功能比你的 basic 更昂贵condition_variable::wait,而且因为condition_variable它是一个低级工具,所以这个非常有用但更昂贵的功能被放在一个单独的类中,这样你只有在使用它时才需要付费。

于 2012-10-27T18:21:00.813 回答
36

它本质上是一个 API 设计决策,以使 API 在默认情况下尽可能安全(额外的开销被视为可以忽略不计)。通过要求传递 aunique_lock而不是 API 的原始mutex用户被引导编写正确的代码(在存在异常的情况下)。

近年来,C++ 语言的重点已经转移到使其默认安全(但如果用户愿意并且足够努力的话,仍然允许用户自食其力)。

于 2012-10-27T14:18:47.617 回答