std :: unique_lock <std :: mutex>或std :: lock_guard <std :: mutex>?</std :: mutex> </std :: mutex>

时间:2013-12-11 10:35:05

标签: c++ multithreading c++11 mutual-exclusion stdmutex

我有两个用例。

一个。我想将两个线程的访问同步到队列。

B中。我希望将两个线程的访问同步到队列并使用条件变量,因为其中一个线程将等待内容被另一个线程存储到队列中。

对于用例A,我使用std::lock_guard<>查看代码示例。对于用例B,我使用std::unique_lock<>查看代码示例。

两者之间有什么区别?我应该在哪个用例中使用哪一个?

7 个答案:

答案 0 :(得分:287)

不同之处在于您可以锁定和解锁std::unique_lockstd::lock_guard仅在施工时锁定一次,在销毁时解锁。

因此,对于用例B,您肯定需要std::unique_lock作为条件变量。如果是A,则取决于您是否需要重新锁定防护装置。

std::unique_lock具有允许它的其他功能:例如,可以在不立即锁定互斥锁的情况下构建,但构建RAII包装器(请参阅here)。

std::lock_guard还提供了方便的RAII包装器,但无法安全地锁定多个互斥锁。当您需要有限范围的包装器时,可以使用它,例如:成员函数:

class MyClass{
    std::mutex my_mutex;
    void member_foo() {
        std::lock_guard<mutex_type> lock(this->my_mutex);            
        /*
         block of code which needs mutual exclusion (e.g. open the same 
         file in multiple threads).
        */

        //mutex is automatically released when lock goes out of scope           
};

要澄清chmike提出的问题,默认情况下std::lock_guardstd::unique_lock是相同的。 因此,在上述情况下,您可以将std::lock_guard替换为std::unique_lock。但是,std::unique_lock可能会有更多的开销。

请注意,现在应该使用std::scoped_lock代替std::lock_guard

答案 1 :(得分:100)

lock_guardunique_lock几乎是一回事; lock_guard是受限制的版本,界面有限。

lock_guard始终拥有从其构造到销毁的锁定。可以在不立即锁定的情况下创建unique_lock,可以在其存在的任何时刻解锁,并且可以将锁的所有权从一个实例转移到另一个实例。

因此,除非您需要lock_guard的功能,否则始终使用unique_lockcondition_variable需要unique_lock

答案 2 :(得分:40)

使用lock_guard除非您需要在不破坏unlock的情况下手动lock互斥互联网。{/ p>

特别是condition_variable在调用wait时进入睡眠状态时会解锁其互斥锁。这就是lock_guard在这里还不够的原因。

答案 3 :(得分:9)

lock_guardunique_lock之间存在某些共同点,并存在某些差异。

但是在问题的上下文中,编译器不允许将lock_guard与条件变量结合使用,因为当线程调用条件变量等待时,互斥锁会自动解锁,而其他时候线程/线程通知并调用当前线程(退出等待),重新获取锁定。

这种现象违反了lock_guard的原则。 lock_guard只能构造一次并且只能构造一次。

因此lock_guard不能与条件变量结合使用,但unique_lock可以是(因为unique_lock可以多次锁定和解锁)。

答案 4 :(得分:1)

一个缺失的区别是: std::unique_lock可以移动,但是std::lock_guard不能移动。

注意:两者都不能复制。

答案 5 :(得分:0)

它们并不是真正的互斥体,lock_guard<muType>std::mutex几乎相同,不同之处在于其生命周期在作用域的结尾处终止(称为D-tor),因此关于这两个互斥锁:

lock_guard<muType>具有一种在作用域范围内拥有互斥量的机制。

unique_lock<muType>是一个包装器,允许延迟锁定,受时间限制的锁定尝试,递归锁定,锁定所有权的转移以及与条件变量一起使用。

以下是示例实现:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <chrono>

using namespace std::chrono;

class Product{

   public:

       Product(int data):mdata(data){
       }

       virtual~Product(){
       }

       bool isReady(){
       return flag;
       }

       void showData(){

        std::cout<<mdata<<std::endl;
       }

       void read(){

         std::this_thread::sleep_for(milliseconds(2000));

         std::lock_guard<std::mutex> guard(mmutex);

         flag = true;

         std::cout<<"Data is ready"<<std::endl;

         cvar.notify_one();

       }

       void task(){

       std::unique_lock<std::mutex> lock(mmutex);

       cvar.wait(lock, [&, this]() mutable throw() -> bool{ return this->isReady(); });

       mdata+=1;

       }

   protected:

    std::condition_variable cvar;
    std::mutex mmutex;
    int mdata;
    bool flag = false;

};

int main(){

     int a = 0;
     Product product(a);

     std::thread reading(product.read, &product);
     std::thread setting(product.task, &product);

     reading.join();
     setting.join();


     product.showData();
    return 0;
}

在此示例中,我将unique_lock<muType>condition variable一起使用

答案 6 :(得分:-5)

正如其他人所提到的,std :: unique_lock跟踪互斥锁的锁定状态,因此您可以将锁定推迟到构造锁之后,并在销毁锁之前解锁。 std :: lock_guard不允许这样做。

似乎没有理由为什么std :: condition_variable等待函数不应该使用lock_guard以及unique_lock,因为每当等待结束时(无论出于何种原因),都会自动重新获取互斥锁,这样就不会导致任何语义违规。但是根据标准,要使用带有条件变量的std :: lock_guard,必须使用std :: condition_variable_any而不是std :: condition_variable。

编辑:已删除“使用pthreads接口std :: condition_variable和std :: condition_variable_any应该相同”。关于gcc的实现:

  • std :: condition_variable :: wait(std :: unique_lock&amp;)只是在底层pthread条件变量上调用pthread_cond_wait()相对于unique_lock所持有的互斥锁(因此对于lock_guard同样可以这样做,但是没有' t因为标准没有规定)
  • std :: condition_variable_any可以使用任何可锁定对象,包括一个根本不是互斥锁的对象(因此它甚至可以与进程间信号量一起使用)