505

I have two use cases.

A. I want to synchronise access to a queue for two threads.

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.

For use case A I see code example using std::lock_guard<>. For use case B I see code example using std::unique_lock<>.

What is the difference between the two and which one should I use in which use case?

Hari
  • 1,561
  • 4
  • 17
  • 26
chmike
  • 20,922
  • 21
  • 83
  • 106
  • 1
    // Need for "Unqiue_Lock" Over "std::Lock_Guard" : (For Conditional Wait()) Why you need the std::unique_lock rather than the std::lock_guard—the waiting thread must unlock the mutex while it’s waiting and lock it again afterward, and "std::lock_guard doesn’t provide that flexibility". If the mutex remained locked while the thread was sleeping, the data-preparation thread wouldn’t be able to lock the mutex to add an item to the queue, and the waiting thread would never be able to see its condition satisfied – ShivaPrasad Gadapa Sep 06 '21 at 15:20
  • 1
    See also: [`std::lock_guard` or `std::scoped_lock`?](https://stackoverflow.com/q/43019598/4561887) – Gabriel Staples Sep 08 '22 at 00:45

7 Answers7

489

The difference is that you can lock and unlock a std::unique_lock. std::lock_guard will be locked only once on construction and unlocked on destruction.

So for use case B you definitely need a std::unique_lock for the condition variable. In case A it depends whether you need to relock the guard.

std::unique_lock has other features that allow it to e.g.: be constructed without locking the mutex immediately but to build the RAII wrapper (see here).

std::lock_guard also provides a convenient RAII wrapper, but cannot lock multiple mutexes safely. It can be used when you need a wrapper for a limited scope, e.g.: 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. So in the above case, you could replace std::lock_guard with std::unique_lock. However, std::unique_lock might have a tad more overhead.

Note that these days (since, C++17) one should use std::scoped_lock instead of std::lock_guard.

Hari
  • 1,561
  • 4
  • 17
  • 26
Stephan Dollberg
  • 32,985
  • 16
  • 81
  • 107
  • 2
    With the instruction std::unique_lock lock(myMutex); will the mutex be locked by the constructor ? – chmike Dec 11 '13 at 10:59
  • 4
    @chmike Yes, it will. Added some clarification. – Stephan Dollberg Dec 11 '13 at 11:00
  • If I'm in use case A, would it be more efficient to use lock_gard instead of unique_lock ? – chmike Dec 11 '13 at 11:05
  • 13
    @chmike Well, I think it's less a question of efficiency than of functionality. If `std::lock_guard` is enough for your case A, then you should use it. Not only it avoids unnecessary overhead but also shows intent to the reader that you will never unlock this guard. – Stephan Dollberg Dec 11 '13 at 11:07
  • 6
    @chmike: Theoretically yes. However Mutices are not exactly lightweight constructs, so the additional overhead of the `unique_lock` is likely to be dwarfed by the cost of actually locking and unlocking the mutex (if the compiler didn't optimize that overhead away, which could be possible). – Grizzly Dec 19 '13 at 09:47
  • 11
    `So for usecase B you definitely need a std::unique_lock for the condition variable` - yes _but_ only in the thread that `cv.wait()`s, because that method atomically releases the mutex. In the other thread where you update the shared variable(s) and then call `cv.notify_one()`, a simple `lock_guard` suffices to lock the mutex in-scope... unless you're doing anything more elaborate that I can't imagine! e.g. http://en.cppreference.com/w/cpp/thread/condition_variable - works for me :) – underscore_d Jul 16 '16 at 17:37
  • 2
    why it's called 'unique'? what exactly is unique on it? – Youda008 Jan 07 '20 at 10:31
  • 2
    It's in contrast to a shared_lock - https://en.cppreference.com/w/cpp/thread/shared_lock (unique vs. share ownerhship). – Stephan Dollberg Jan 08 '20 at 20:53
  • The last sentence's prescription to avoid lock_guard in favour of C++17's scoped_lock is unexplained, and seems to contradict [this accepted answer](https://stackoverflow.com/questions/43019598/stdlock-guard-or-stdscoped-lock/60172828) to a similar question. I would be grateful to find a justification one way or another, primarily based in a discussion of performance (overhead). – Charles Savoie Jul 07 '23 at 11:01
  • @underscore_d right. And furthermore, in theory, we don't even need the `lock_guard` before doing `cv.notify_one()` or `cv.notify_all()` if there's no shared variable or I/O operation, etc(i.e. no chance of threads conflict). – starriet Jul 11 '23 at 22:46
  • @underscore_d Oh, but if we want to _manually_ unlock the mutex before `cv.notify_one()`(as done in condition_variable [example](https://en.cppreference.com/w/cpp/thread/condition_variable) in cppreference.com), then we need `unique_lock`, since `lock_guard` doesn't have `unlock` function. Just for future reference for myself ;) – starriet Jul 16 '23 at 09:02
165

lock_guard and unique_lock are pretty much the same thing; lock_guard is a restricted version with a limited interface.

A lock_guard always holds a lock from its construction to its destruction. 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.

So you always use lock_guard, unless you need the capabilities of unique_lock. A condition_variable needs a unique_lock.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • 17
    `A condition_variable needs a unique_lock.` - yes _but_ only on the `wait()`ing side, as elaborated in my comment to inf. – underscore_d Jul 16 '16 at 17:41
  • _"A `unique_lock` can be created without immediately locking."_ Can you demonstrate this? By default, however, the mutex is automatically locked at `unique_lock` construction, right?--same as a `lock_guard`? – Gabriel Staples Sep 07 '22 at 16:41
  • 1
    I figured it out: 1) how to create a lock which automatically locks the mutex upon construction & unlocks it when it exits scope: `std::unique_lock lock(my_mutex);`, versus: 2) how to create a lock which does NOT automatically lock the mutex upon construction, but which will still ensure it is unlocked when it exits scope: `std::unique_lock lock(my_mutex, std::defer_lock);`. See here: https://en.cppreference.com/w/cpp/thread/unique_lock/unique_lock and here: https://en.cppreference.com/w/cpp/thread/lock_tag: `std::defer_lock`: _"do not acquire ownership of the mutex"_. – Gabriel Staples Sep 08 '22 at 00:32
73

Use lock_guard unless you need to be able to manually unlock the mutex in between without destroying the lock.

In particular, condition_variable unlocks its mutex when going to sleep upon calls to wait. That is why a lock_guard is not sufficient here.

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.

ComicSansMS
  • 51,484
  • 14
  • 155
  • 166
  • Passing a lock_guard to one of the conditional variable's wait methods would be fine because the mutex is always reacquired when the wait ends, for whatever reason. However the standard only provides an interface for unique_lock. This could be regarded as a deficiency in the standard. – Chris Vine Dec 17 '13 at 23:30
  • 3
    @Chris You'd still break encapsulation in this case. The wait method would need to be able to extract the mutex from the `lock_guard` and unlock it, thus temporarily breaking the class invariant of the guard. Even though this happens invisible to the user, I would consider that a legitimate reason for not allowing the use of `lock_guard` in this case. – ComicSansMS Dec 18 '13 at 08:03
  • If so, it would be invisible and undetectable. gcc-4.8 does it. wait(unique_lock&) calls __gthread_cond_wait(&_M_cond, __lock.mutex()->native_handle()) (see libstdc++-v3/src/c++11/condition_variable.cc), which calls pthread_cond_wait() (see libgcc/gthr-posix.h). The same could be done for lock_guard (but isn't because it is not in the standard for condition_variable). – Chris Vine Dec 18 '13 at 11:42
  • 5
    @Chris The point is `lock_guard` does not allow retrieving the underlying mutex at all. This is a deliberate limitation to allow simpler reasoning about code that uses `lock_guard` as opposed to code that uses a `unique_lock`. The only way to achieve what you ask is by deliberately breaking encapsulation of the `lock_guard` class and exposing its implementation to a different class (in this case the `condition_variable`). This is a tough price to pay for the questionable advantage of the user of a condition variable not having to remember the difference between the two lock types. – ComicSansMS Dec 18 '13 at 12:00
  • Passing a lock_guard to std::condition_variable would no more break encapsulation than passing a unique_lock, and no more than passing lock_guard to std::condition_variable_any, which is entirely permissible already. I suspect the reason why condition_variable only takes unique_lock is that unique_lock is the more generally useful mutex lock type, and expanding the API for a relatively miniscule increase in efficiency was not thought worthwhile. – Chris Vine Dec 18 '13 at 21:46
  • 5
    @Chris Where did you get the idea that `condition_variable_any.wait` would work with a `lock_guard`? The standard requires the provided Lock type to meet the [`BasicLockable`](http://en.cppreference.com/w/cpp/concept/BasicLockable) requirement (§30.5.2), which `lock_guard` does not. Only its underlying mutex does, but for reasons I pointed out earlier the interface of `lock_guard` does not provide access to the mutex. – ComicSansMS Dec 19 '13 at 10:08
11

There are certain common things between lock_guard and unique_lock and certain differences.

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.

This phenomenon is against the principle of lock_guard. lock_guard can be constructed only once and destructed only once.

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).

Dean Seo
  • 5,486
  • 3
  • 30
  • 49
Sandeep
  • 1,237
  • 1
  • 14
  • 29
  • 5
    `he compiler does not allow using a lock_guard in combination with a condition variable` This is false. It certainly **does** allow and work perfectly with a `lock_guard` on the `notify()`ing side. Only the `wait()`int side requires a `unique_lock`, because `wait()` must release the lock while checking for the condition. – underscore_d Jul 16 '16 at 17:43
5

One missing difference is: std::unique_lock can be moved but std::lock_guard can't be moved.

Note: Both cant be copied.

stateMachine
  • 5,227
  • 4
  • 13
  • 29
user3500315
  • 61
  • 1
  • 4
-1

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> has a mechanism for owning a mutex for the duration of a scoped block.

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.

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

rekkalmd
  • 171
  • 1
  • 12
-5

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::lock_guard does not permit this.

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. 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.

Edit: deleted "Using the pthreads interface std::condition_variable and std::condition_variable_any should be identical". On looking at gcc's implementation:

  • 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_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)
Chris Vine
  • 677
  • 3
  • 7