EmbeddedRelated.com

Mutex

Category: Rtos | Also known as: mutexes, mutual exclusion

A mutex (mutual exclusion object) is a synchronization primitive that allows at most one task or thread to hold it at a time, protecting a shared resource or critical section from concurrent access. Unlike a binary semaphore, a mutex carries ownership: only the task that acquired it is permitted to release it.

In practice

In embedded RTOS work, mutexes appear wherever multiple tasks share a resource that cannot be safely accessed concurrently — a SPI bus driver, a UART transmit buffer, a block of shared memory, or a linked list. The typical pattern is: acquire the mutex before entering the critical section, perform the operation, then release the mutex. Any other task that calls acquire while the mutex is held will block until the holder releases it. On FreeRTOS this is `xSemaphoreTake` / `xSemaphoreGive` on a handle created with `xSemaphoreCreateMutex`; on CMSIS-RTOS v2 it is `osMutexAcquire` / `osMutexRelease`.

A critical property of most RTOS mutex implementations is priority inheritance. If a high-priority task blocks waiting for a mutex held by a low-priority task, the RTOS temporarily elevates the holder's priority to that of the waiter, reducing the window in which a medium-priority task can preempt the holder and delay the high-priority task. This directly addresses the classic priority inversion problem. FreeRTOS provides this via `xSemaphoreCreateMutex` (which enables inheritance) as distinct from `xSemaphoreCreateBinary` (which does not). Priority inheritance support varies across RTOS implementations: some always apply it for mutexes, some offer multiple mutex types with different behavior, and some do not provide it at all.

A common pitfall is holding a mutex for too long. Lengthy critical sections raise worst-case blocking latency for all other tasks competing for the same mutex, which can violate timing requirements. Another frequent mistake is calling mutex acquire from an ISR context; most RTOS implementations explicitly forbid this, because ISRs do not participate in task scheduling and cannot block. For ISR-to-task communication, a semaphore or a queue is usually the correct tool. Recursive mutexes (available in FreeRTOS as `xSemaphoreCreateRecursiveMutex`) allow the same task to acquire the mutex multiple times without deadlocking, at the cost of requiring a matching number of releases.

Deadlock is the other major hazard. If Task A holds Mutex 1 and waits for Mutex 2 while Task B holds Mutex 2 and waits for Mutex 1, both tasks block forever. Consistent lock ordering across the codebase — always acquiring mutexes in the same global sequence — is the standard preventive measure. The blog posts "Mutex vs. Semaphore - Part 1" and "Mutex vs. Semaphores – Part 2: The Mutex & Mutual Exclusion Problems" cover these failure modes in detail.

Frequently asked

What is the difference between a mutex and a binary semaphore?
The key distinction is ownership. A mutex can only be released by the task that acquired it; a binary semaphore has no such restriction and can be signaled from a different context, including an ISR. This distinction is common across RTOS implementations, though the exact behavior is API-specific and some implementations blur the boundary. Because of this ownership model, mutexes can support priority inheritance to mitigate priority inversion, while binary semaphores typically cannot. Use a mutex to protect a shared resource; use a binary semaphore for task-synchronization or ISR-to-task signaling.
Can I use a mutex from an ISR?
No, on virtually all RTOS implementations. ISRs cannot block, and acquiring a mutex requires the ability to block if the mutex is already held. Calling a blocking mutex API from an ISR is either undefined behavior or will trigger a fault. For signaling a task from an ISR, use a semaphore with an ISR-safe API (e.g., FreeRTOS's `xSemaphoreGiveFromISR`) or post to a queue.
What is priority inversion and how does mutex priority inheritance help?
Priority inversion occurs when a high-priority task is forced to wait for a low-priority task to release a mutex, and a medium-priority task preempts the low-priority holder in the meantime, indirectly delaying the high-priority task. Priority inheritance counters this by temporarily raising the holder's effective priority to match the highest-priority waiter for that mutex, shrinking the window in which the medium-priority task can interfere. It reduces but does not eliminate the problem; unbounded priority inversion requires more comprehensive design measures.
Do I need an RTOS to use mutual exclusion?
No. On bare-metal systems, mutual exclusion is commonly achieved by disabling interrupts around a critical section, or on single-core MCUs by using atomic read-modify-write operations. Double-buffering and other structural techniques can also eliminate the need for explicit locking. The blog post 'Scorchers, Part 3: Bare-Metal Concurrency With Double-Buffering and the Revolving Fireplace' illustrates one such approach. RTOS mutexes are convenient when you have multiple preemptible tasks and need the scheduler to handle blocking automatically.
What is a recursive mutex and when should I use one?
A recursive mutex allows the same task to acquire the mutex multiple times without deadlocking itself, provided it releases it the same number of times. This is useful when a function that already holds a mutex calls another function that also tries to acquire the same mutex. FreeRTOS provides this via `xSemaphoreCreateRecursiveMutex`. Recursive mutexes add overhead and can obscure lock-ordering problems, so they should be used deliberately rather than as a default choice.

Differentiators vs similar concepts

A mutex is often conflated with a binary semaphore because both are binary-state primitives. The decisive difference is ownership and its consequences: mutexes enforce that the acquiring task is the only one that can release, enabling priority inheritance and making accidental cross-task releases a detectable error. Binary semaphores lack ownership and are better suited for signaling. Mutexes are also distinct from spinlocks: a spinlock busy-waits (consuming CPU cycles) rather than blocking the calling task. The core tradeoff is CPU waste while spinning versus the overhead of blocking and context-switching; spinlocks can be appropriate for very short critical sections where that wait time is bounded and predictable, but become wasteful when wait time is variable or long, regardless of core count.