views:

347

answers:

3

I read this somewhere in SO: "recursive mutex has a sense of ownership, the thread that grabs the mutex must be the same thread that releases the mutex. For non-recursive mutexes, there is no sense of ownership and any thread can release the mutex no matter which thread originally locked the mutex."

I'm confused by the last statement. Can one thread lock a mutex and another different thread unlock that mutex? I thought that same thread should be the only one able to unlock the mutex? Or is there any specific mutex that allows this? I hope someone can clarify.

+1  A: 

Recursive mutexes are thread-specific by design (the same thread locking again = recursion, another thread locking meanwhile = block). Regular mutexes do not have this design and thus they can in fact be locked and unlocked in different threads.

Tronic
You say regular mutexes can be locked and unlocked by different threads. Is this implementation dependent?
jasonline
Quite possibly it is.
Tronic
Are you sure that you are not referring to a binary semaphore used as a lock? Mutexes are owned and can only be unlocked by the thread that locks them. Whereas people have a habit of "acquiring and releasing" semaphores and using them as locks.
D.Shawley
+4  A: 

Non-recursive mutex

Most mutexes are (or at least should be) non-recursive. A mutex is an object which can be acquired or released atomically, which allows data which is shared between multiple threads to be protected against race conditions, data corruption, and other nasty things.

A single mutex should only ever be acquired once by a single thread within the same call chain. Attempting to acquire (or hold) the same mutex twice, within the same thread context, should be considered an invalid scenario, and should be handled appropriately (usually via an ASSERT as you are breaking a fundamental contract of your code).

Recursive mutex

This should be considered a code smell, or a hack. The only way in which a recursive mutex differs from a standard mutex is that a recursive mutex can be acquired multiple times by the same thread.

The root cause of the need for a recursive mutex is lack of ownership and no clear purpose or delineation between classes. For example, your code may call into another class, which then calls back into your class. The starting class could then try to acquire the same mutex again, and because you want to avoid a crash, you implement this as a recursive mutex.

This kind of topsy-turvy class hierarchy can lead to all sorts of headaches, and a recursive mutex provides only a band-aid solution for a more fundamental architectural problem.


Regardless of the mutex type, it should always be the same thread which acquires and releases the same mutex. The general pattern that you use in code is something like this:

Thread 1

    Acquire mutex A
    // Modify or read shared data
    Release mutex A

Thread 2

    Attempt to acquire mutex A
    Block as thread 1 has mutex A
    When thread 1 has released mutex A, acquire it
    // Modify or read shared data
    Release mutex A

It gets more complicated when you have multiple mutexes that can be acquired simultaneously (say, mutex A and B). There is the risk that you'll run into a deadlock situation like this:

Thread 1

    Acquire mutex A
    // Access some data...

*** Context switch to thread 2 ***

Thread 2

    Acquire mutex B
    // Access some data

*** Context switch to thread 1 ***

    Attempt to acquire mutex B
    Wait for thread 2 to release mutex B

*** Context switch to thread 2 ***

    Attempt to acquire mutex A
    Wait for thread 1 to release mutex A

*** DEADLOCK ***

We now have a situation where each thread is waiting for the other thread to release the other lock -- this is known as the ABBA deadlock pattern.

To prevent this situation, it is important that each thread always acquires the mutexes in the same order (e.g. always A, then B).

LeopardSkinPillBoxHat
Basing from your answer, for non-recursive mutex can only be unlocked by the thread that locked it, right.
jasonline
@jasonline - Yes, the very nature of mutexes is that the thread which acquires it, releases it. If another thread could release a mutex acquired by another thread, what purpose would the mutex achieve?
LeopardSkinPillBoxHat
Thank you for your clarification. I added a reference to the source of my question. I guess it was referring to a semaphore.
jasonline
What about when two public methods acquire/release a mutex, and one of them calls the other? That would require a recursive mutex. This is extremely common in Java.
John Kugelman
A mutex is a specific type of semaphore which incorporates additional features - see this article for more information: http://en.wikipedia.org/wiki/Semaphore_%28programming%29
LeopardSkinPillBoxHat
@JohnKugelman - I would prevent the need for a recursive mutex by creating an API layer (front-end)and an implementation layer (back end). You then enforce a policy that locks are always acquired by the implementation layer, which means that the 2nd public method will only acquire the mutex once (in the implementation layer of the 1st public method).
LeopardSkinPillBoxHat
Yes, I think it's clear: Mutexes are meant to be used for mutual exclusion (post/release operation is restricted to thread which called pend/acquire) only and binary semaphores are meant to be used for event notification (post-ability from any thread) and mutual exclusion.
jasonline
@Leopard - I'm not sure why you're jumping through hoops to eliminate the recursive mutex, though. You say it's a code smell but aside from being slightly less efficient I don't see the "fundamental architectural problem".
John Kugelman
@JohnKugelman - I think recursive mutexes have been tarnished for me because I have experienced a lot of issues in code I work with which uses them (mostly deadlocks in a multi-threaded application). For a simple API, it's likely they won't cause you problems, as long as they are acquired in a consistent fashion (and the proper policy is well-documented). The application I work in uses them to protect major data structures throughout the application, which is why we have experienced so many headaches.
LeopardSkinPillBoxHat
+1  A: 

I think this covers all your questions. Straight from linux man pages for pthreads:

If the mutex type is PTHREAD_MUTEX_NORMAL, deadlock detection shall not be provided. Attempting to relock the mutex causes deadlock. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, undefined behavior results.

If the mutex type is PTHREAD_MUTEX_ERRORCHECK, then error checking shall be provided. If a thread attempts to relock a mutex that it has already locked, an error shall be returned. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, an error shall be returned.

If the mutex type is PTHREAD_MUTEX_RECURSIVE, then the mutex shall maintain the concept of a lock count. When a thread successfully acquires a mutex for the first time, the lock count shall be set to one. Every time a thread relocks this mutex, the lock count shall be incremented by one. Each time the thread unlocks the mutex, the lock count shall be decremented by one. When the lock count reaches zero, the mutex shall become available for other threads to acquire. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, an error shall be returned.

If the mutex type is PTHREAD_MUTEX_DEFAULT, attempting to recursively lock the mutex results in undefined behavior. Attempting to unlock the mutex if it was not locked by the calling thread results in undefined behavior. Attempting to unlock the mutex if it is not locked results in undefined behavior.

Duck
Yes, it's clear. Thanks for the additional information.
jasonline