我有两个用例。

a .我想同步两个线程对队列的访问。

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

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

这两者之间的区别是什么?我应该在哪个用例中使用哪个?


当前回答

正如其他人所提到的,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可以用于任何可锁对象,包括一个根本不是互斥锁的对象(因此它甚至可以用于进程间信号量)

其他回答

正如其他人所提到的,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可以用于任何可锁对象,包括一个根本不是互斥锁的对象(因此它甚至可以用于进程间信号量)

使用lock_guard,除非您需要在不破坏锁的情况下手动解锁互斥锁。

特别是,当调用等待时进入睡眠状态时,condition_variable会解锁它的互斥锁。这就是为什么lock_guard在这里是不够的。

如果您已经使用c++ 17或更高版本,可以考虑使用scoped_lock作为lock_guard的稍微改进版本,具有相同的基本功能。

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

注意:两者都不能复制。

它们并不是相同的互斥对象,lock_guard<muType>与std::mutex几乎相同,不同之处在于它的生命周期在作用域的末尾结束(称为D-tor),因此关于这两个互斥对象的明确定义:

lock_guard<muType>具有在作用域块期间拥有互斥锁的机制。

And

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>带条件变量

lock_guard和unique_lock之间有一些共同点,也有一些不同之处。

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

这种现象违背了lock_guard的原则。Lock_guard只能被构造一次,也只能被销毁一次。

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