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

时间:2012-10-27 11:14:34

标签: c++ multithreading c++11 mutex

在使用std::unique_lock时,我对std::condition_variable的作用感到有些困惑。据我了解documentationstd::unique_lock基本上是一个膨胀的锁定保护,可以在两个锁之间交换状态。

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

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

2 个答案:

答案 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
}
  1. 此代码非常简单。
  2. 这是例外安全。
  3. wait函数可以检查lk.owns_lock(),如果是false则抛出异常。
  4. 这些是推动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 ++语言的重点已经转移到默认情况下使其安全(但仍然允许用户在他们想要并且努力尝试时自己动手)。