4
votes

I am having problems with deadlocking in my program. So I have been reading about the locks, but the problem is most information is inconsistent or not platform defined. At the Recursive Lock (Mutex) vs Non-Recursive Lock (Mutex) the most accepted answer says:

Because the recursive mutex has a sense of ownership, the thread that grabs the mutex must be the same thread that release the mutex. In the case of non-recursive mutexes, there is no sense of ownership and any thread can usually release the mutex no matter which thread originally took the mutex. In many cases, this type of "mutex" is really more of a semaphore action, where you are not necessarily using the mutex as an exclusion device but use it as synchronization or signaling device between two or more threads.

In the commentaries people say that is not correct and there is no reference about it. So...

1) If I lock a non-recursive mutex in thread A. Can thread B unlock it without grabbing the lock?

2) If in a lock was taken in a Non-Recursive mutex by thread A and thread B calls to get the lock, will thread B wait until the lock has been released to get the lock or will it throw an exception? What about this case in a recursive mutex? (Also discussed in other questions where no decent conclusion could be made)

3) When using recursive locks, on process termination, do all my recursive locks have to be released? (Depending where the process ends that does not happen)

4) What issues am I looking at when using a combination of recursive and non-recursive locks with caution?

PS: Using only windows platform and std::thread.

4

4 Answers

8
votes

I think you'll be helped immensely by reading the wiki on Reentrant Mutexes. I would agree with the comments in that other thread; The accepted answer is wrong, or at least explains its point very, very poorly.

All Mutexes have a notion of ownership. That's what makes them different from a Semaphore. The thread that locks a mutex is always the thread that has to unlock it, and that's part of why mutexes can cause deadlock, but also why they work for their intended purpose (to mutually exclude access to a particular block of code).

So what is the difference between a Recursive/Reenrant and regular Mutex? A Recursive mutex can be locked multiple times by the same thread. To quote the wiki:

Recursive locks (also called recursive thread mutex) are those that allow a thread to recursively acquire the same lock that it is holding. Note that this behavior is different from a normal lock. In the normal case if a thread that is already holding a normal lock attempts to acquire the same lock again, then it will deadlock.

This is the entirety of the difference between the two mutex types. Essentially, you need a recursive mutex if you're placing a mutex lock inside a recursive method and the method recurses before the mutex is freed. Otherwise after the first recursion, there will be instant deadlock because the lock cannot be acquired a second time.

Really, this is the only reason to use a recursive mutex; Most other situations where you'd get the same thread trying to acquire the same lock without freeing it can probably be refactored into properly acquiring/freeing the lock without the need for a recursive mutex. And doing so will be much safer; A recursive function is naturally going to bubble out and free every lock on the recursive mutex assuming RAII, where as in other situations you may not sufficiently free the mutex and still wind up with deadlock.

So, to answer your specific questions:

  1. No, unless your mutex system specifically allows this
  2. Yes in both cases generally, though again this is mutex implementation specific with regards to blocking/throwing. Almost every system I've ever used just blocks (and that's why non-recursive mutexes deadlock if the same thread locks twice without a free)
  3. Yes, usually, though generally they will be freed assuming proper RAII and the process terminates gracefully. Processes terminating non-gracefully while holding locks can be a bit of a crapshoot.
  4. See my above explanations. Specifically, draw attention to the following from the wiki:

Note that the recursive lock is said to be released if and only if the number of times it has been acquired matches the number of times it has been released by the owner thread.

1
votes

You are referring to POSIX mutexes discussion but Windows does not support POSIX anyway and you use c++ standard threading primitives that may differ in details.

So better check standard library documentation first, for instance c++ standard for unlock explicitly states:

Requires: The calling thread shall own the mutex.

1
votes

The following is from Linux pthread_mutex_lock man page,

Variables of type pthread_mutex_t can also be initialized statically, using the constants PTHREAD_MUTEX_INITIALIZER (for fast mutexes), THREAD_RECURSIVE_MUTEX_INITIALIZER_NP (for recursive mutexes), and PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP (for error checking mutexes).

On error checking'' andrecursive'' mutexes, pthread_mutex_unlock actually checks at run-time that the mutex is locked on entrance, and that it was locked by the same thread that is now calling pthread_mutex_unlock. If these conditions are not met, an error code is returned and the mutex remains unchanged. ``Fast'' mutexes perform no such checks, thus allowing a locked mutex to be unlocked by a thread other than its owner. This is non-portable behavior and must not be relied upon.

It appears that "a locked mutex may be unlocked by a thread other than its owner with non-recursive mutex kind"

0
votes

Really, you should write a simple program to test these cases.

  1. Assuming we're properly using a mutex, another thread should never unlock a mutex. It may be possible for any thread to unlock a mutex. (edit: I have never tried unlocking a mutex with another thread. That would defeat the purpose) This would cause race conditions.

  2. Consider the code:

    void criticalSection(){
        pthread_mutex_lock(&mutex)
        //dostuff
        pthread_mutex_unlock(&mutex)
    }
    

    If there are two threads, thread A and B, and thread A enters the function first, it acquires the lock and does stuff. If thread B enters while thread A is still in the function, it will be locked. When thread A executes

      pthread_mutex_unlock(&mutex)
    

    Thread B will now be "woken up" and begin to do stuff.

  3. I suppose you could do whatever you want, but if there are recursive locks that means a thread is still doing something and you probably want to wait for it to finish.

  4. I suppose you're looking at a multi-threaded application with no race conditions. :-)