简体   繁体   English

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

[英]std::unique_lock<std::mutex> or std::lock_guard<std::mutex>?

I have two use cases.我有两个用例。

A. I want to synchronise access to a queue for two threads. A. 我想同步两个线程对一个队列的访问。

B. I want to synchronise access to a queue for two threads and use a condition variable because one of the threads will wait on content to be stored into the queue by the other thread. B. 我想同步两个线程对队列的访问并使用条件变量,因为其中一个线程将等待另一个线程将内容存储到队列中。

For use case AI see code example using std::lock_guard<> .对于用例 AI,请参阅使用std::lock_guard<>代码示例。 For use case BI see code example using std::unique_lock<> .对于用例 BI,请参阅使用std::unique_lock<>代码示例。

What is the difference between the two and which one should I use in which use case?两者之间有什么区别,我应该在哪个用例中使用哪个?

The difference is that you can lock and unlock a std::unique_lock .不同之处在于您可以锁定和解锁std::unique_lock std::lock_guard will be locked only once on construction and unlocked on destruction. std::lock_guard只会在构造时锁定一次,并在销毁时解锁。

So for use case B you definitely need a std::unique_lock for the condition variable.因此,对于用例 B,您肯定需要一个std::unique_lock作为条件变量。 In case A it depends whether you need to relock the guard.在 A 情况下,这取决于您是否需要重新锁定警卫。

std::unique_lock has other features that allow it to eg: be constructed without locking the mutex immediately but to build the RAII wrapper (see here ). std::unique_lock具有其他功能,例如:在不立即锁定互斥锁的情况下构造它,而是构建 RAII 包装器(参见此处)。

std::lock_guard also provides a convenient RAII wrapper, but cannot lock multiple mutexes safely. std::lock_guard还提供了一个方便的 RAII 包装器,但不能安全地锁定多个互斥锁。 It can be used when you need a wrapper for a limited scope, eg: a member function:当您需要有限范围的包装器时可以使用它,例如:成员函数:

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
    }           
};

To clarify a question by chmike, by default std::lock_guard and std::unique_lock are the same.为了澄清 chmike 的问题,默认情况下std::lock_guardstd::unique_lock是相同的。 So in the above case, you could replace std::lock_guard with std::unique_lock .因此,在上述情况下,您可以将std::lock_guard替换为std::unique_lock However, std::unique_lock might have a tad more overhead.但是, std::unique_lock可能会有更多的开销。

Note that these days (since, C++17) one should use std::scoped_lock instead of std::lock_guard .请注意,现在(从 C++17 开始)应该使用std::scoped_lock而不是std::lock_guard

lock_guard and unique_lock are pretty much the same thing; lock_guardunique_lock几乎是一回事; lock_guard is a restricted version with a limited interface. lock_guard是具有有限接口的受限版本。

A lock_guard always holds a lock from its construction to its destruction. lock_guard从构造到销毁始终持有锁。 A unique_lock can be created without immediately locking, can unlock at any point in its existence, and can transfer ownership of the lock from one instance to another. unique_lock可以在不立即锁定的情况下创建,可以在其存在的任何时候解锁,并且可以将锁的所有权从一个实例转移到另一个实例。

So you always use lock_guard , unless you need the capabilities of unique_lock .所以你总是使用lock_guard ,除非你需要unique_lock的功能。 A condition_variable needs a unique_lock .一个condition_variable需要一个unique_lock

Use lock_guard unless you need to be able to manually unlock the mutex in between without destroying the lock .除非您需要能够在不破坏lock情况下手动unlock互斥锁,否则请使用lock_guard

In particular, condition_variable unlocks its mutex when going to sleep upon calls to wait .特别是, condition_variable在调用wait后进入睡眠状态时会解锁其互斥锁。 That is why a lock_guard is not sufficient here.这就是为什么lock_guard在这里是不够的。

If you're already on C++17 or later, consider using scoped_lock as a slightly improved version of lock_guard , with the same essential capabilities.如果您已经使用 C++17 或更高版本,请考虑使用scoped_lock作为lock_guard的略微改进版本,具有相同的基本功能。

There are certain common things between lock_guard and unique_lock and certain differences. lock_guardunique_lock之间有一些共同点,也有一些区别。

But in the context of the question asked, the compiler does not allow using a lock_guard in combination with a condition variable, because when a thread calls wait on a condition variable, the mutex gets unlocked automatically and when other thread/threads notify and the current thread is invoked (comes out of wait), the lock is re-acquired.但是在所问问题的上下文中,编译器不允许将lock_guard与条件变量结合使用,因为当线程调用条件变量上的等待时,互斥锁会自动解锁,并且当其他线程/线程通知和当前线程被调用(退出等待),重新获取锁。

This phenomenon is against the principle of lock_guard .这种现象违反了lock_guard的原则。 lock_guard can be constructed only once and destructed only once. lock_guard只能构造一次,只能销毁一次。

Hence lock_guard cannot be used in combination with a condition variable, but a unique_lock can be (because unique_lock can be locked and unlocked several times).因此lock_guard不能与条件变量结合使用,但unique_lock可以(因为unique_lock可以多次锁定和解锁)。

One missing difference is: std::unique_lock can be moved but std::lock_guard can't be moved.一个缺失的区别是: std::unique_lock可以移动但std::lock_guard不能移动。

Note: Both cant be copied.注意:两者都不能复制。

They are not really same mutexes, lock_guard<muType> has nearly the same as std::mutex , with a difference that it's lifetime ends at the end of the scope (D-tor called) so a clear definition about these two mutexes :它们并不是真正相同的互斥锁, lock_guard<muType>几乎与std::mutex相同,不同之处在于它的生命周期在范围的末尾(D-tor 调用)结束,因此对这两个互斥锁有明确的定义:

lock_guard<muType> has a mechanism for owning a mutex for the duration of a scoped block. lock_guard<muType>具有在作用域块的持续时间内拥有互斥锁的机制。

And并且

unique_lock<muType> is a wrapper allowing deferred locking, time-constrained attempts at locking, recursive locking, transfer of lock ownership, and use with condition variables. unique_lock<muType>是一个包装器,允许延迟锁定、有时间限制的锁定尝试、递归锁定、锁定所有权的转移以及与条件变量一起使用。

Here is an example implemetation :这是一个示例实现:

#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;
}

In this example, i used the unique_lock<muType> with condition variable在这个例子中,我使用了unique_lock<muType>condition variable

As has been mentioned by others, std::unique_lock tracks the locked status of the mutex, so you can defer locking until after construction of the lock, and unlock before destruction of the lock.正如其他人提到的, std::unique_lock 跟踪互斥锁的锁定状态,因此您可以将锁定推迟到锁构造之后,并在锁销毁之前解锁。 std::lock_guard does not permit this. std::lock_guard 不允许这样做。

There seems no reason why the std::condition_variable wait functions should not take a lock_guard as well as a unique_lock, because whenever a wait ends (for whatever reason) the mutex is automatically reacquired so that would not cause any semantic violation.似乎没有理由为什么 std::condition_variable 等待函数不应该采用 lock_guard 和 unique_lock,因为每当等待结束(无论出于何种原因)互斥量都会自动重新获取,这样就不会导致任何语义违规。 However according to the standard, to use a std::lock_guard with a condition variable you have to use a std::condition_variable_any instead of std::condition_variable.但是,根据标准,要将 std::lock_guard 与条件变量一起使用,您必须使用 std::condition_variable_any 而不是 std::condition_variable。

Edit : deleted "Using the pthreads interface std::condition_variable and std::condition_variable_any should be identical".编辑:删除“使用 pthreads 接口 std::condition_variable 和 std::condition_variable_any 应该是相同的”。 On looking at gcc's implementation:在查看 gcc 的实现时:

  • std::condition_variable::wait(std::unique_lock&) just calls pthread_cond_wait() on the underlying pthread condition variable with respect to the mutex held by unique_lock (and so could equally do the same for lock_guard, but doesn't because the standard doesn't provide for that) std::condition_variable::wait(std::unique_lock&) 只是在底层 pthread 条件变量上调用关于 unique_lock 持有的互斥锁的 pthread_cond_wait() (因此同样可以对 lock_guard 做同样的事情,但不是因为标准没有提供)
  • std::condition_variable_any can work with any lockable object, including one which is not a mutex lock at all (it could therefore even work with an inter-process semaphore) std::condition_variable_any 可以与任何可锁定对象一起工作,包括一个根本不是互斥锁的对象(因此它甚至可以与进程间信号量一起工作)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM