C + + 11: 为什么 std: : 條_variable 使用 std: : only_lock?

在使用 std::condition_variable时,我对 std::unique_lock的作用有点困惑。就我对 文件的理解而言,std::unique_lock基本上是一个臃肿的锁保护,可以在两个锁之间交换状态。

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

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

23107 次浏览

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

最近几年,C + + 语言的焦点已经转移到默认情况下的安全性上(但仍然允许用户如果想要并且足够努力的话,可以自己站起来)。

所以没有技术原因?

我支持 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. 如果是 false,则 wait函数可以检查 lk.owns_lock()并引发异常。

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

此外,condition_variable::wait不接受 lock_guard<mutex>,因为您可以这样说: 在 lock_guard<mutex>销毁之前,我拥有这个互斥锁。但是当您调用 condition_variable::wait时,您隐式地释放了互斥锁。因此,该操作与 lock_guard用例/语句不一致。

无论如何,我们需要 unique_lock,这样就可以从函数返回锁,将它们放入容器,并以异常安全的方式在非范围模式中锁定/解锁互斥对象,因此 unique_lockcondition_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>数据成员。

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