Eduard Rostomyan
Eduard Rostomyan

Reputation: 6546

Why we keep mutex instead of declaring it before guard every time?

Please consider this classical approach, I have simplified it to highlight the exact question:

#include <iostream>
#include <mutex>
using namespace std;

class Test
{
    public:
        void modify()
        {
            std::lock_guard<std::mutex> guard(m_);
            // modify data
        }
    private:
    /// some private data
    std::mutex m_;
};

This is the classical approach of using std::mutex to avoid data races.

The question is why are we keeping an extra std::mutex in our class? Why can't we declare it every time before the declaration of std::lock_guard like this?

void modify()
{
    std::mutex m_;
    std::lock_guard<std::mutex> guard(m_);
   // modify data
 }

Upvotes: 7

Views: 1440

Answers (5)

mfnx
mfnx

Reputation: 3018

You need a mutex at class level. Otherwise, each thread has a mutex for itself, and therefore the mutex has no effect.

If for some reason you don't want your mutex to be stored in a class attribute, you could use a static mutex as shown below.

void modify()
{
    static std::mutex myMutex;
    std::lock_guard<std::mutex> guard(myMutex);
    // modify data
}

Note that here there is only 1 mutex for all the class instances. If the mutex is stored in an attribute, you would have one mutex per class instance. Depending on your needs, you might prefer one solution or the other.

Upvotes: 3

Stephan Lechner
Stephan Lechner

Reputation: 35154

The misunderstanding comes from what the mutex is and what the lock_guard is good for.

A mutex is an object that is shared among different threads, and each thread can lock and release the mutex. That's how synchronization among different threads works. So you can work with m_.lock() and m_.unlock() as well, yet you have to be very careful that all code paths (including exceptional exits) in your function actually unlocks the mutex.

To avoid the pitfall of missing unlocks, a lock_guard is a wrapper object which locks the mutex at wrapper object creation and unlocks it at wrapper object destruction. Since the wrapper object is an object with automatic storage duration, you will never miss an unlock - that's why.

A local mutex does not make sense, as it would be local and not a shared ressource. A local lock_guard perfectly makes sense, as the autmoatic storage duration prevents missing locks / unlocks.

Hope it helps.

Upvotes: 7

slawekwin
slawekwin

Reputation: 6310

Synchronization of threads involves checking if there is another thread executing the critical section. A mutex is the objects that holds the state for us to check if it was "locked" by a thread. lock_guard on the other hand is a wrapper that locks the mutex on initialization and unlocks it during destruction.

Having realized that, it should be clearer why there has to be only one instance of the mutex that all lock_guards need access to - they need to check if it's clear to enter the critical section against the same object. In the second snippet of your question each function call creates a separate mutex that is seen and accessible only in its local context.

Upvotes: 3

The Quantum Physicist
The Quantum Physicist

Reputation: 26286

This all depends on the context of what you want to prevent from being executed in parallel.

A mutex will work when multiple threads try to access the same mutex object. So when 2 threads try to access and acquire the lock of a mutex object, only one of them will succeed.

Now in your second example, if two threads call modify(), each thread will have its own instance of that mutex, so nothing will stop them from running that function in parallel as if there's no mutex.

So to answer your question: It depends on the context. The mission of the design is to ensure that all threads that should not be executed in parallel will hit the same mutex object at the critical part.

Upvotes: 4

Naveen
Naveen

Reputation: 73443

Lets say two threads are calling modify in parallel. So each thread gets its own, new mutex. So the guard has no effect as each guard is locking a different mutex. The resource you are trying to protect from race-conditions will be exposed.

Upvotes: 21

Related Questions