A mutex is like a lock with one key. Only its owner may enter the protected critical section; other threads wait until the key becomes available.
A simulator showing three threads acquiring a mutex and waiting in a queue
Use each thread's button to call lock() or unlock()
When one thread holds the mutex, another thread's lock() joins the queue. Once the owner unlocks, the first waiting thread acquires it in this simulation.
A mutex has an owner
Only the thread that locks a mutex should unlock it. Unlocking from another thread violates the mutex's ownership rules; behavior depends on the implementation. A binary semaphore also has two states, but has no owner and can be signaled by another thread.
A timeline comparing priority inversion and priority inheritance
Priority inversion: high-priority H waits for low-priority L's mutex
Play the timeline one time unit at a time. Compare when H finishes with and without priority inheritance.
Priority inversion
Low-priority L holds a mutex that high-priority H needs, so H must wait. The problem becomes worse when unrelated medium-priority M preempts L. L cannot release the mutex while M runs, so H is delayed by M despite having a higher priority.
Priority inversion caused repeated resets on the Mars Pathfinder mission in 1997.
Priority inheritance
When H blocks on L's mutex, the operating system temporarily raises L to H's priority. M can no longer preempt L, which can finish the critical section and release the mutex. L then returns to its original priority.
In FreeRTOS, xSemaphoreCreateMutex() creates a mutex with priority inheritance, whereas xSemaphoreCreateBinary() creates a binary semaphore without it. This distinction matters when protecting shared resources.