10

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.

Patryk
  • 22,602
  • 44
  • 128
  • 244
TheWaterProgrammer
  • 7,055
  • 12
  • 70
  • 159

3 Answers3

7

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

tmlen
  • 8,533
  • 5
  • 31
  • 84
6

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 ...
...
A. Terstegge
  • 191
  • 2
  • 4
  • 1
    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? – Howard Hinnant Jan 16 '17 at 20:23
  • Could you add some more details (compiler, error message, code)? Maybe it would also be cleaner to create a new question. – A. Terstegge Jan 17 '17 at 22:05
  • 1
    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: http://codepad.org/8Xvh8O1m Does your code compile for you? With what compiler? – Howard Hinnant Jan 17 '17 at 22:58
  • 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. – A. Terstegge Jan 18 '17 at 22:17
  • 1
    Upvoted for your honest and investigative response. And I upvoted the other two correct answers as well. – Howard Hinnant Jan 19 '17 at 01:35
5

Impossible, but you don’t actually need that.

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

Soonts
  • 20,079
  • 9
  • 57
  • 130