Francis Cugler
Francis Cugler

Reputation: 7895

Using mutex, lock_guard, unique_lock properly in a class

I'm trying to understand std::mutex, std::lock_guard, std::unique_lock better when working with a class.

For starters I sort of know the difference between lock_guard and unique_lock: I know that lock_guard only locks the mutex on construction which is a preferred use when using it in a class member function as such:

class Foo {
    std::mutex myMutex;

public:
    void someFunc() {
        std::lock_guard<std::mutex> guard( myMutex );

        // code
    }
};

As in the above the lock_guard with the class's member myMutex will be locked in the beginning of the scope of the function Foo::someFunc() then unlocked after the code leaves the scope due to the destructors of both lock_guard and mutex.

I also understand that unique_lock allows you to lock & unlock a mutex multiple times.

My question is about a design of a class to where; what If I want the mutex to be locked in the constructor of the class, then not to have it unlock when the constructor goes out of scope, but to unlock when the destructor of the class is called...

class Foo {
    std::mutex myMutex;
public:
    Foo() {
        // lock mutex here;
    }

    ~Foo() {
        // unlock mutex here;
    }
};

Can the above be achieved; and if so how?

In the last example above what I'm not sure about is: if a lock_guard is used in the constructor of the class; will it go out of scope after the constructor leaves scope, or when the class's destructor is invoked when the object of the class goes out of scope? I like to try to mimic the desired behavior of the 2nd example shown.

Upvotes: 2

Views: 2368

Answers (1)

Francis Cugler
Francis Cugler

Reputation: 7895

After I had posted this question: I did a little more research and some trial and errors. With that I have opted towards a different implementation and solution.

Instead of what I was originally proposing, I ended up using std::shared_mutex and std:shared_lock.

So in my class's header I'm not saving or storing any mutex. Now in my class's cpp file. I'm using a static global shared_mutex

So my class now looks like this:

Foo.cpp

#include "Foo.h"

#include <mutex>

std::mutex g_mutex;

Foo::Foo() {
    // code not locked

    {   // scope of guard
        std::lock_guard<std::mutex> lock( g_mutex );
        // code to lock
    } // end scope destroy guard unlock mutex

    // other class code
}

Foo::someFunc() {
    // Code to lock
    std::lock_guard<std::mutex> lock( g_mutex );
}

I had discovered why it wasn't working properly for me. In my class's constructor it was calling a function from its parent or base class. The parent or base class was then calling a static member of this class and the static member function of this class was also using lock_guard on the same mutex.

After I found the problem; I had two options. I could of used 2 independent mutexes, one for the constructor specifically and one for the static method specifically. After some thought I figured well if I use 2, and I'm blocking the full constructor the current lock and mutex won't go out of scope until the class instance is destroyed; however the life of the class will be nearly the full life of the application. Then if I used a second mutex and lock guard within the static method, it would be redundant to wrap another lock_guard around an existing one. So I came to the conclusion that I needed to create a scoped block { } for the code that needed to be blocked by the mutex so that it can be unlocked after this section goes out of scope, then the constructor is free to call the static method and it can reuse the same mutex as it is now free. The class is now working properly and it is not crashing nor throwing exceptions when it shouldn't be.

Upvotes: 1

Related Questions