Mutex

Also known as: mutual exclusion lock

On this page 7

A mutex (short for mutual exclusion lock) is a synchronization primitive that ensures only one thread at a time can hold it and therefore execute the protected code, known as the critical section. In concurrency, a mutex protects shared data from race conditions by forcing threads that want the same resource to take turns.

A thread acquires (locks) the mutex before touching shared state and releases (unlocks) it afterwards. Any other thread that tries to acquire it in the meantime is blocked or told to retry. Mutexes are among the most basic tools of thread safety in programming, available in practically every language and operating system, from POSIX pthread_mutex_t to std::mutex in C++, sync.Mutex in Go and synchronized blocks in Java.

How a mutex enforces mutual exclusion

Internally, a mutex is built on an atomic operation such as compare-and-swap or test-and-set, which changes a lock word from unlocked to locked in one indivisible step. If the operation succeeds, the thread owns the lock; if it fails, another thread already holds it.

Modern implementations are usually hybrid locks. A thread first spins briefly, retrying the atomic operation in case the owner releases the lock soon, and then asks the operating system to put it to sleep until the lock becomes free. On Linux, this sleeping path uses the futex system call, so an uncontended lock costs only a few atomic instructions and no kernel call at all.

Lock and unlock operations also act as memory barriers. Everything a thread writes inside the critical section becomes visible to the next thread that acquires the same mutex. This happens-before guarantee is as important as the exclusion itself, because without it compilers and processors could reorder memory accesses.

Types and variants of mutexes

Libraries offer several mutex variants that differ in ownership rules, fairness and access patterns. Choosing the right one depends on how the protected data is used.

VariantBehaviorTypical use
Plain mutexOne owner, locking twice by the same thread deadlocks or failsDefault protection of shared state
Recursive mutexSame thread may lock repeatedly, must unlock equally oftenLegacy code with nested calls
Read-write lockMany readers or one writer at a timeData read often and changed rarely
Timed mutexAcquisition gives up after a timeoutAvoiding indefinite blocking
SpinlockBusy-waits instead of sleepingVery short sections, kernel code
Common mutex variants

A defining property of a mutex is ownership: only the thread that locked it may unlock it. Some implementations detect violations, while others produce undefined behavior, which is a frequent source of subtle bugs.

A mutex is often confused with a semaphore and with atomic operations. They solve related problems but provide different guarantees, as the comparison shows.

PrimitiveWhat it controlsOwnership
MutexExclusive access by one threadYes, owner must unlock
Binary semaphoreA permit count of 0 or 1No, any thread may signal
Counting semaphoreUp to N concurrent holdersNo ownership
Atomic operationOne variable updated indivisiblyNot applicable, no lock held
Condition variableWaiting until a condition becomes trueUsed together with a mutex
Mutex and neighboring concurrency primitives

A mutex is the right tool when several variables must change together consistently. A single counter or flag can often use an atomic variable instead, which avoids blocking. When a thread must wait for a state change, such as a queue becoming non-empty, a condition variable paired with the mutex is the standard pattern.

Using a mutex correctly

The safest way to use a mutex is through scoped locking, where a guard object acquires the lock and releases it automatically when the scope ends, even if an exception is thrown. Examples are std::lock_guard in C++, defer mu.Unlock() in Go and the with statement in Python.

Critical sections should be as short as possible. Holding a lock while performing network calls, disk I/O or logging forces every other thread to wait for slow operations, turning the mutex into a serialization bottleneck that removes the benefit of parallelism.

ProblemCauseRemedy
DeadlockThreads acquire several locks in different ordersGlobal lock ordering, lock several at once
Lock contentionMany threads compete for one hot lockFiner-grained locks, sharding, less work inside
Priority inversionLow-priority owner blocks a high-priority threadPriority inheritance mutexes
Forgotten unlockEarly return or exception skips releaseScoped guards, RAII
Data race despite lockingSome code paths access data without the lockConsistent locking discipline, race detectors
Mutex problems and remedies

Performance and design considerations

An uncontended mutex is cheap, typically costing tens of nanoseconds. A contended mutex is expensive, because threads sleep, wake up and move between processor cores, which can cost microseconds per handoff and destroys cache locality.

Fairness is another design choice. Many default mutexes are deliberately unfair, letting a running thread grab a just-released lock before a sleeping waiter wakes up, because this maximizes throughput. Fair or queue-based locks hand the mutex to waiters in arrival order, which prevents starvation but usually lowers overall performance.

  • Measure contention with profilers before replacing a mutex with complex lock-free code.
  • Split coarse locks so that independent data has independent protection.
  • Prefer message passing or the actor model where sharing state is not essential.
  • Avoid locks in signal handlers and interrupt contexts, where blocking is unsafe.
  • Document which mutex protects which data, so future changes keep the discipline.

In plain words

Think of a single-occupancy restroom with one key hanging by the door. Whoever takes the key can go in; everyone else waits until the key comes back. A mutex is that key for a piece of shared data in a program.

Without the key, two parts of a program could change the same information at the same moment and leave it in a broken state. The mutex makes them take turns. The rule that matters most is simple: return the key quickly, or the line outside grows long.

A real-world example

A ticketing service sells seats for a concert. Several worker threads process purchases at the same time, and each one checks whether a seat is free and then marks it as sold. Without protection, two threads can both see the same seat as free and sell it twice.

The developers protect the seat map with a mutex per section of the venue. A thread locks the section, checks and updates the seat, and unlocks it, keeping the critical section to a few microseconds. Payment processing, which can take seconds, happens outside the lock.

During a sale that draws thousands of buyers, double bookings disappear, and because each section has its own lock, purchases in different sections proceed in parallel. Correctness improves without a visible loss of throughput.