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

luc*_*nte 70 c++ multithreading mutex c++11

我对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优化的事实是什么?如果是这样,它究竟是如何更快?

How*_*ant 101

所以没有技术原因?

我赞成了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();
}
Run Code Online (Sandbox Code Playgroud)

这正是一个人不应该使用的方式condition_variable.在标有以下内容的地区:

// mut locked by this thread here
Run Code Online (Sandbox Code Playgroud)

存在异常安全问题,这是一个严重的问题.如果在这些区域(或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
}
Run Code Online (Sandbox Code Playgroud)

这要好得多,但仍然不是很好.该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
}
Run Code Online (Sandbox Code Playgroud)
  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`
Run Code Online (Sandbox Code Playgroud)

使用此condition_variable 适配器,可以等待任何可锁定类型.如果有成员lock()unlock(),你是好去.正确实现condition_variable_any需要condition_variable数据成员和shared_ptr<mutex>数据成员.

因为这个新功能比你的基本功能更昂贵condition_variable::wait,并且因为这condition_variable是一个低级别的工具,这个非常有用但更昂贵的功能被放入一个单独的类中,这样你只需要付费就可以使用它.

  • 哇,我对你的回答感到震惊.非常感谢这个细节! (5认同)

cme*_*erw 34

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

近年来,C++语言的重点已转向默认安全(但仍然允许用户在他们想要并且努力尝试时自己动手).