50

Please, can anybody explain how to use and create an unique_lock in c++? It should be used both to get mutual exclusion to any procedure of the monitor and to be able to perform wait() on the condition variable...I'm not understanding from the documentation how I am supposed to create it. Is necessary a mutex? Here is a pseudo-code:

/* compile with g++, flags -std=c++0x -lpthread */

#include <condition_variable>
#include <mutex>
#include <thread>
#include <iostream>
#include <string.h>
#include <unistd.h>

class monitorTh {

private:

    std::mutex m;
    std::condition_variable waitP;
    std::condition_variable waitC;
    char element[32];
    std::unique_lock::unique_lock l;

public:
    void produce(char* elemProd) {
        l.lock();
        if (/*already_present_element*/) {
            waitP.wait(l);
        }
        else {/*produce element*/}
        l.unlock();
    }

    void consume() {
        /*something specular*/
    }
};

int main(int argc, char* argv[]) {

    monitorTh* monitor = new monitorTh();
    char prodotto[32] = "oggetto";

    std::thread producer([&]() {
        monitor->produce(prodotto);
    });

    std::thread consumer([&]() {
        monitor->consume();
    });

    producer.join();
    consumer.join();
}
Patrizio Bertoni
  • 2,582
  • 31
  • 43
SagittariusA
  • 5,289
  • 15
  • 73
  • 127

4 Answers4

73

std::unique_lock use the RAII pattern.

When you want to lock a mutex, you create a local variable of type std::unique_lock passing the mutex as parameter. When the unique_lock is constructed it will lock the mutex, and it gets destructed it will unlock the mutex. More importantly: If a exceptions is thrown, the std::unique_lock destructer will be called and so the mutex will be unlocked.

Example:

#include<mutex>
int some_shared_var=0;

int func() {
    int a = 3;
    { //Critical section
        std::unique_lock<std::mutex> lock(my_mutex);
        some_shared_var += a;
    } //End of critical section
}        
Saral Garg
  • 132
  • 7
André Puel
  • 8,741
  • 9
  • 52
  • 83
11

A more detailed sample code using condition variables:

#include<mutex>
std::mutex(mu); //Global variable or place within class
std::condition_variable condition; //A signal that can be used to communicate between functions

auto MyFunction()->void
{
  std::unique_lock<mutex> lock(mu);
  //Do Stuff
  lock.unlock(); //Unlock the mutex
  condition.notify_one(); //Notify MyOtherFunction that this is done
}

auto MyOtherFunction()->void
{
   std::unique_lock<mutex> lock(mu);
   condition.wait(lock) //Wait for MyFunction to finish, a lambda can be passed also to protects against spurious wake up e.g (lock,[](){return *some condition*})
   lock.unlock();
}
Babra Cunningham
  • 2,949
  • 1
  • 23
  • 50
  • 10
    the explicit unlocking is unnecessary. look up how a unique_lock() behaves. – pcodex Mar 05 '18 at 08:20
  • 1
    @Prab, just to give the explanation: unique_lock() is automatically released when the destructor is called meaning it is exception safe and that it automatically unlocks when you leave scope. – EliSquared Mar 11 '18 at 00:26
  • 1
    @EliSquared you're repeating what I've already mentioned in my comment to the author – pcodex Mar 13 '18 at 11:10
  • 1
    @pcodex cppreference states about the explicit unlocking: "Manual unlocking is done before notifying, to avoid waking up the waiting thread only to block again (see notify_one for details)". – Leherenn Oct 26 '20 at 15:48
  • @Leherenn by using a unique lock you are granting control over the mutex to the lock object. That is the whole point of RAII. There is no need to manually unlock. – pcodex Oct 26 '20 at 18:05
  • 2
    @pcodex In the second case I agree, but the first one seems valid to me if performance are important. The condition will potentially be notified before the mutex is unlocked otherwise. Or am I missing something? – Leherenn Nov 02 '20 at 14:00
  • @Leherenn you might consider putting "Do Stuff" inside its on scoped block together with the lock. That would be more in the RAII spirit, and you might also save a few cpu instructions for what it's worth. – ThreeStarProgrammer57 Sep 29 '22 at 22:41
6

std::unique_lock<std::mutex> holds a lock on a separate std::mutex object. You associate the lock object with the mutex by passing it in the constructor. Unless you specify otherwise, the mutex will be immediately locked. If the lock object holds the lock when it is destroyed then the destructor will release the lock. Typically, the std::unique_lock<std::mutex> object will thus be a local variable, declared at the point where you wish to acquire the lock.

In your case, the produce() function could be written like this:

void produce(char* elemProd) {
    std::unique_lock<std::mutex> lk(m); // lock the mutex
    while (/*already_present_element*/) { // condition variable waits may wake spuriously
        waitP.wait(lk);
    }
    {/*produce element*/}
    // lk releases the lock when it is destroyed
}

Note that I have replaced the if with a while to account for spurious wakes from the wait() call.

Anthony Williams
  • 66,628
  • 14
  • 133
  • 155
-7

In this case, I think all you need to do is:

m.lock();
// Critical section code
m.unlock();
DigitalInBlue
  • 299
  • 3
  • 10