Is possible to use std::condition_variable with std::lock_guard?
Asked Answered
R

3

14

I am using a std::condition_variable combined with a std::unique_lock like this.

std::mutex a_mutex;
std::condition_variable a_condition_variable;
std::unique_lock<std::mutex> a_lock(a_mutex);
a_condition_variable.wait(a_lock, [this] {return something;});
//Do something
a_lock.unlock();

It works fine. As I understand, std::condition_variable accepts a std::unique_lock for it to wait. But, I am trying to combine it with std::lock_guard but not able to.

My question is: Is it possible to replace std::unique_lock with a std::lock_guard instead ? This can relieve me from manually unlocking the lock every time I use it.

Rancid answered 13/11, 2016 at 18:59 Comment(0)
F
12

No, a std::unique_lock is needed if it is used with std::condition_variable. std::lock_guard may have less overhead, but it cannot be used with std::condition_variable.

But the std::unique_lock doesn't need to be manually unlocked, it also unlocks when it goes out of scope, like std::lock_guard. So the waiting code could be written as:

std::mutex a_mutex;
std::condition_variable a_condition_variable;
{
    std::unique_lock<std::mutex> a_lock(a_mutex);
    a_condition_variable.wait(a_lock, [this] {return something;});
    //Do something
}

See http://en.cppreference.com/w/cpp/thread/unique_lock

Figueroa answered 13/11, 2016 at 19:8 Comment(0)
F
7

Any call to wait() on a condition variable will always need to lock() and unlock() the underlying mutex. Since the wrapper lock_guard<> does not provide these functions, it can never be used with wait().

Still you could write your own simple mutex wrapper based on lock_guard<>, and add the 2 necessary methods. Additionally you would have to use condition_variable_any, which accepts any lock/mutex with a lock()/unlock() interface:

#include <mutex>

template<typename _mutex_t>
class my_lock_guard
{
public:
    explicit my_lock_guard(_mutex_t & __m) : __mutex(__m)
    { __mutex.lock(); }

    my_lock_guard(_mutex_t & __m, std::adopt_lock_t) : __mutex(__m)
    { } // calling thread owns mutex

    ~my_lock_guard()
    { __mutex.unlock(); }

    void lock()
    { __mutex.lock(); }

    void unlock()
    { __mutex.unlock(); }   

    my_lock_guard(const my_lock_guard &) = delete;
    my_lock_guard& operator=(const my_lock_guard &) = delete;

private:
    _mutex_t &  __mutex;
};

And then:

#include <condition_variable>
...
std::mutex m;
my_lock_guard<std::mutex> lg(m);
std::condition_variable_any cva;
cva.wait(lg, [] { return something;});  
// do something ...
...
Franzoni answered 16/1, 2017 at 17:39 Comment(5)
I can't seem to get lock_guard to work with condition_variable_any. The error message says something about missing member functions lock() and unlock(). Why is that?Marpet
Could you add some more details (compiler, error message, code)? Maybe it would also be cleaner to create a new question.Franzoni
I used your first code snipped, add includes, main, and modified the lambda to just return true. I'm using the latest clang supplied by macOS. Here are the error messages: codepad.org/8Xvh8O1m Does your code compile for you? With what compiler?Marpet
Shame on me... In my test code I accidently passed the mutex and not the lock_guard<> as the parameter to wait()... The wait() will definitely need to lock and unlock the underlying mutex, and since the lock_guard<> is so dead simple that it does not even provide these two functions, you will never be able to use it with wait(). I changed my answer accordingly. Thanks for this important hint.Franzoni
Upvoted for your honest and investigative response. And I upvoted the other two correct answers as well.Marpet
N
6

Impossible, but you don’t actually need that.

std::unique_lock automatically unlocks itself in destructor, if it was locked.

Northwestward answered 13/11, 2016 at 19:8 Comment(1)
thanks for such a quick clarification. Both answers are acceptable btw. Just chose one of bothRancid

© 2022 - 2024 — McMap. All rights reserved.