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


348

我有两个用例。

答:我想通过两个线程同步对队列的访问。

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

对于用例AI,请参见使用的代码示例std::lock_guard<>。有关用例BI,请参见使用的代码示例std::unique_lock<>

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

Answers:


344

区别在于您可以锁定和解锁std::unique_lockstd::lock_guard只会在施工时锁定一次,在破坏时解锁。

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

std::unique_lock具有其他功能,例如:无需立即锁定互斥体即可构建,而是构建RAII包装器(请参见此处)。

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_guardstd::unique_lock。但是,std::unique_lock可能会有更多的开销。

请注意,这几天应该使用std::scoped_lock而不是std::lock_guard


2
使用指令std :: unique_lock <std :: mutex> lock(myMutex); 互斥锁会被构造函数锁定吗?
chmike 2013年

3
@chmike是的,它将。添加了一些说明。
2013年

10
@chmike好吧,我认为这不是效率问题,而是功能问题。如果std::lock_guard足以满足您的情况A,则应使用它。它不仅避免了不必要的开销,而且还向读者表明了您永远不会解锁此防护装置的意图。
Stephan Dollberg

5
@chmike:理论上是。但是Mutice并不是完全轻量级的结构,因此unique_lock,实际锁定和解锁互斥锁的成本(如果编译器没有优化该开销,则可能)会使mutex 的额外开销相形见)。
灰熊

6
So for usecase B you definitely need a std::unique_lock for the condition variable-是的,仅在cv.wait()s 线程中,因为该方法自动释放互斥体。在另一个线程中,您更新共享变量然后调用cv.notify_one(),一个简单lock_guard的锁就足以锁定互斥锁...除非您做了我无法想象的更复杂的事情!例如en.cppreference.com/w/cpp/thread/condition_variable-对我有用 :)
underscore_d

114

lock_guard并且unique_lock是几乎同样的事情; lock_guard是具有受限接口的受限版本。

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

因此lock_guard,除非您需要的功能,否则请始终使用unique_lock。A condition_variable需要一个unique_lock


11
A condition_variable needs a unique_lock.-是的,仅在wait()ing方面,如我对inf的评论所述。
underscore_d

48

使用lock_guard除非您需要能够手动unlock之间的互斥锁而不破坏lock

特别是,condition_variable在呼叫时进入睡眠状态时,解锁其互斥锁wait。因此,lock_guard此处a 不够。


将lock_guard传递给条件变量的wait方法之一会很好,因为无论出于何种原因,总是在等待结束时重新获取互斥量。但是,该标准仅提供用于unique_lock的接口。这可能被视为标准中的不足。
克里斯·藤

3
@Chris在这种情况下,您仍然会破坏封装。wait方法将需要能够从中提取互斥锁lock_guard并将其解锁,从而临时破坏防护的类不变性。即使这种情况对用户来说是不可见的,我仍认为这是lock_guard在这种情况下不允许使用的正当理由。
ComicSansMS

如果是这样,它将是不可见和不可检测的。gcc-4.8做到了。wait(unique_lock <mutex>&)调用__gthread_cond_wait(&_ M_cond,__lock.mutex()-> native_handle())(请参阅libstdc ++-v3 / src / c ++ 11 / condition_variable.cc),该函数将调用pthread_cond_wait()(请参见libgcc /gthr-posix.h)。可以对lock_guard进行相同的操作(但不是因为condition_variable的标准中没有此功能)。
克里斯·藤

4
@Chris的要点是lock_guard根本不允许检索基础互斥量。这是一个有意的限制,允许lock_guard相对于使用的代码,对使用的代码进行更简单的推理unique_lock。实现您所要求的唯一方法是故意破坏lock_guard该类的封装,并将其实现公开给其他类(在本例中为condition_variable)。对于条件变量的用户不必记住两种锁类型之间的区别而付出的可疑优势,这是一个艰难的代价。
ComicSansMS 2013年

4
@Chris您从哪里得到condition_variable_any.wait可以与a一起使用的想法lock_guard?该标准要求提供的Lock类型满足BasicLockable要求(第30.5.2节),lock_guard但不满足。仅它的底层互斥锁可以使用,但是出于我之前指出的原因,该接口lock_guard不提供对互斥锁的访问。
ComicSansMS 2013年

11

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

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

这种现象违反了原理lock_guardlock_guard只能被构造一次,而只能被破坏一次。

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


5
he compiler does not allow using a lock_guard in combination with a condition variable这是错误的。这当然不会允许,并用完美的作品lock_guardnotify()荷兰国际集团的一面。只有wait()int端需要unique_lock,因为wait()在检查条件时必须释放锁。
underscore_d

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>withcondition variable


-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&)只是针对与unique_lock持有的互斥锁在底层pthread条件变量上调用pthread_cond_wait()(因此,对lock_guard可以同样执行此操作,但不是因为标准没有为此提供)
  • std :: condition_variable_any可以与任何可锁定对象一起使用,包括根本不是互斥锁的对象(因此甚至可以与进程间信号灯一起使用)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.