Mutexes: mutual exclusion locks
Mutual exclusion locks, or mutexes, are the simplest of the synchronization services. A mutex is used to ensure exclusive access to data shared between threads.
A mutex is typically acquired (pthread_mutex_lock() or pthread_mutex_timedlock()) and released (pthread_mutex_unlock()) around the code that accesses the shared data (usually a critical section).
Only one thread may have the mutex locked at any given time. Threads attempting to lock an already locked mutex will block until the thread that owns the mutex unlocks it. When the thread unlocks the mutex, the highest-priority thread waiting to lock the mutex will unblock and become the new owner of the mutex. In this way, threads will sequence through a critical region in priority-order.
In most situations, acquisition of a mutex doesn't require entry to the kernel for a free mutex. What allows this is the use of the compare-and-swap opcode on x86 processors and the load/store conditional opcodes on most ARM processors.
Entry to the kernel is necessary at acquisition time if the mutex is already held, so that the thread can go on a blocked list; kernel entry is done on exit if other threads are waiting to be unblocked on that mutex. This allows normal acquisition and release of an uncontested critical section or resource to be very quick, incurring work by the OS only to resolve contention.
A nonblocking lock function (pthread_mutex_trylock()) can be used to test whether the mutex is currently locked or not. For best performance, the execution time of the critical section should be small and of bounded duration. A condvar should be used if the thread may block within the critical section.
Priority inheritance and mutexes
By default, if a thread with a higher priority than the mutex owner attempts to lock a mutex, then the effective priority of the owner is increased to that of the blocked higher-priority thread (but see below). The owner's effective priority is again adjusted when it unlocks the mutex; its new priority is the maximum of its own priority and the priorities of those threads it still blocks, either directly or indirectly.
This scheme not only ensures that the higher-priority thread will be blocked waiting for the mutex for the shortest possible time, but also solves the classic priority-inversion problem.
The pthread_mutexattr_init() function sets the protocol to PTHREAD_PRIO_INHERIT to allow this behavior; you can call pthread_mutexattr_setprotocol() to override this setting. The pthread_mutex_trylock() function doesn't change the thread priorities because it doesn't block.
What happens if the thread that's waiting for the mutex is running at a privileged priority,
and the mutex owner's process doesn't have the PROCMGR_AID_PRIORITY ability enabled?
In this case, the thread that owns the mutex is boosted to the highest unprivileged priority.
For more information, see
Scheduling priority
earlier in this chapter and
procmgr_ability()
in the C Library Reference.
Other attributes
You can also modify other mutex attributes before initializing a mutex:
- Use pthread_mutexattr_settype() to allow a mutex to be recursively locked by the same thread. This can be useful to allow a thread to call a routine that might attempt to lock a mutex that the thread already happens to have locked.
- Use pthread_mutexattr_setrobust() to set the mutex's robustness, which helps you recover the mutex if its owner terminates while holding it.
- Use
pthread_mutexattr_setprioceiling()
to set the priority ceiling.
After you set a mutex's priority ceiling, you should not try to lock that mutex from any process that has a priority that's greater than the mutex's priority ceiling. If you attempt this, the mutex locking will fail.
Note that robust mutexes and mutexes with priority ceilings use more system resources than other mutexes.