6
votes

I'm new to threads and trying to understand the mutex.
I understand mutex as some object ( key ) which is picked by only one thread ( if it's picked then the other threads can't pick it and have to wait ) to access some part of code which we want to lock.
So only one thread has access to that locked part of code at the time ( for example shared counter ). The other threads will have to wait until mutex is unlocked and so on.

Mutex1.Lock();
 {
     Mutex2.Lock();
     {
          // Code locked by mutex 1 and 2.
     }
     Mutex2.Unlock();

     // Code locked by mutex 1.
 }
 Mutex1.Unlock();

What happens if I write multiple mutex locks?
Will both mutexes get picked by the same thread? I also read that multiple mutex locks may cause deadlock.
Could anyone explain and provide me an example of how could I cause deadlock by locking part of code with 2 mutexes?

3
"could I cause deadlock": Thread (1) lock mutex (A); Thread (2) lock mutex (B); Thread (1) try to lock Mutex (B); Thread (2) try to lock Mutex (A). Now Thread (1) is waiting for Thread (2) to free its lock and Thread (2) is waiting for Thread (1) to free its lock. Neither thread can make any progress.Richard Critten
If Thread(1) locks mutex A then thread(2) cannot access to that part of code ( mutex B is inside of locked mutex A ) or am I missing something?stilltryingbutstillsofar
There is no need for the mutex(s) (attempt at plural) to be in the same section of code. You might have more than one code path that updates or reads a shared variable. These multiple paths need to be protected from simultaneous writes (or write/reads).Richard Critten
Easy way to avoid deadlocks - always lock in the same order (and never escalate a lock - such as change a read-only lock to a write lock).Andrew Henle
You might want to check std::recursive_mutexJVApen

3 Answers

5
votes

A thread can hold multiple locks, yes. And a deadlock might occur indeed even if it has acquired only one mutex.. Look at the workflow:

Thread A

. 
.
.
lock mutex1
.
<---- Context switch! ----->

Thread B

.
.
.
.
lock mutex2
.
.
.
try lock mutex1 ----> BLOCKED UNTIL THREAD A RELEASES LOCK!

Thread A

.
.
.
try lock mutex2 ---> BLOCKED UNTIL THREAD B RELEASES LOCK!

DEADLOCK!

Read this, some mutex types, like the PTHREAD_MUTEX_NORMAL might cause a thread to deadlock it self as well.

.
.
.
lock mutex
.
.
.
try lock
7
votes

Here's the safe way to code what you describe:

std::mutex Mutex1;
std::mutex Mutex2;

void
test()
{
    using namespace std;
    lock(Mutex1, Mutex2);  // no deadlock!
    lock_guard<mutex> lk1{Mutex1, adopt_lock};
    {
        lock_guard<mutex> lk2{Mutex2, adopt_lock};
        // Code locked by mutex 1 and 2.
    }   // lk2.unlock()
    // Code locked by mutex 1.
}   // lk1.unlock()

This code can't deadlock because std::lock(Mutex1, Mutex2) locks both mutexes while avoiding deadlock (by some internal algorithm). An advantage of using std::lock is that you don't have to remember what order you need to lock your mutexes in (which makes maintenance easier in a large code base). But the disadvantage is that you need to lock both locks at a single point in your code. If you can't lock them at the same time, then you have to fall back to ordering as the other answers describe.

This code is also exception safe in that if any exception is thrown anywhere, whatever happens to be locked is unlocked as the exception propagates out.

2
votes

What happens if I write multiple mutex locks? Will both mutexes picked by the same thread?

A thread can simultaneously hold any number of mutexes. In your example code, a thread that enters the region protected by Mutex1 will attempt to acquire Mutex2, waiting for another thread to release it first if necessary. There is no particular reason in the code you present to think that it would not succeed.

I also read that multiple mutex locks may cause deadlock. Could anyone explain me how could I cause deadlock by locking part of code with 2 mutexes?

Suppose there are two threads, one holding mutex1 and the other holding mutex2 (in which case at least the latter must be running something different from your pseudocode). Now suppose that each thread attempts to acquire the other mutex without releasing the one it already holds. The first thread must acquire mutex2 to proceed, and cannot do so until the other releases it. The second thread must acquire mutex1 to proceed, and cannot do so until the other releases it. Ergo, neither thread can ever proceed -- this is a deadlock.

The general rule is this: if there is a set of mutexes, all of which two or more threads may each want to hold similtaneously, then there must be a fixed relative ordering of those mutexes that every thread honors when acquiring any subset of them.