【问题标题】:C++11: why does std::condition_variable use std::unique_lock?C++11:为什么 std::condition_variable 使用 std::unique_lock?
【发布时间】:2012-10-17 11:17:54
【问题描述】:

在与std::condition_variable 合作时,我对std::unique_lock 的角色有点困惑。据我了解documentationstd::unique_lock 基本上是一个臃肿的锁守卫,可以在两个锁之间交换状态。

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

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

【问题讨论】:

  • 您是否对为什么需要带有条件变量的锁/互斥锁,或者锁和互斥锁之间的区别,或者为什么条件变量使用唯一锁而不是互斥锁感到困惑?
  • "为什么条件变量使用唯一锁而不是互斥锁" this.

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


【解决方案1】:

所以没有技术原因?

我赞成 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() 在这里会是灾难性的。

现在让我们看看代码是如何使用带有unique_lock&lt;mutex&gt; 的实际condition_variable API 编写的:

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,则抛出异常。

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

此外,condition_variable::wait 不采用lock_guard&lt;mutex&gt;,因为lock_guard&lt;mutex&gt; 就是你所说的:我拥有这个互斥锁的锁,直到lock_guard&lt;mutex&gt; 销毁。但是当您调用condition_variable::wait 时,您会隐式释放互斥锁上的锁。因此该操作与lock_guard 用例/语句不一致。

无论如何,我们都需要unique_lock,以便可以从函数返回锁,将它们放入容器中,并以异常安全的方式锁定/解锁非作用域模式中的互斥锁,因此unique_lock 是@987654351 的自然选择@。

更新

bamboon 在下面的 cmets 中建议我对比 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&lt;mutex&gt; 数据成员。

因为这个新功能比你的基本condition_variable::wait 更昂贵,而且因为condition_variable 是一个如此低级的工具,所以这个非常有用但更昂贵的功能被放在一个单独的类中,这样你只需在以下情况下付费你用它。

【讨论】:

  • 你能澄清一下“它”吗?你说的是lock_guard,还是condition_variable,或者condition_variable::wait
  • 抱歉,算了。我完全忘记了condition_variable_any
  • 啊,模板condition_variable::wait。是的,condition_variable_any 是要走的路。功能未折叠到condition_variable 的原因是它更昂贵。而condition_variable 是一个如此低级的工具,它需要尽可能高效。如果您使用condition_variable_any,您只需为添加的功能付费。
  • 好的,感谢您提供的额外信息。也许将其添加到您的答案中,因为可能会将 condition_variable_any 与普通互斥锁一起滥用。
  • 哇,你的回答让我大吃一惊。非常感谢您提供如此详细的信息!
【解决方案2】:

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

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

【讨论】:

  • 所以没有技术原因?
猜你喜欢
  • 2012-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-24
  • 1970-01-01
  • 2016-09-07
  • 1970-01-01
  • 2014-03-12
相关资源
最近更新 更多