absl::Mutex Internal State Machine: Lock/Unlock State Transitions Explained
absl::Mutex implements a strict two-state machine with Free and Exclusive states, where lock() atomically transitions Free→Exclusive (blocking if already held), unlock() transitions Exclusive→Free, and any deviation such as re-locking by the owner or unlocking when free triggers undefined behavior and debug-mode assertions.
The absl::Mutex class in the abseil/abseil-cpp repository provides high-performance synchronization primitives for C++ applications. Its internal state machine governs all exclusive lock operations and enforces strict non-reentrancy rules to prevent deadlock and data races. This analysis examines the state transitions defined in absl/synchronization/mutex.h and their implementation in the synchronization library.
The Two-State Exclusive Lock Machine
absl::Mutex maintains a simple state machine for exclusive (write) locks with exactly two states:
- Free: The mutex is unowned and available for acquisition.
- Exclusive: The mutex is owned by a single thread, blocking all other lock attempts.
The valid transitions follow a strict cycle:
| State | Operation | Next State |
|---|---|---|
| Free | lock() |
Exclusive |
| Exclusive | unlock() |
Free |
Any operation that violates this cycle—such as calling unlock() on a Free mutex or attempting to lock() while already holding the mutex—results in undefined behavior. In debug builds, these violations trigger immediate program termination via assertion failures.
Lock Operation State Transitions (lock() / Lock())
When a thread invokes lock() (or the capitalized Lock() method), the implementation examines the current state:
- If Free: The calling thread immediately becomes the exclusive owner. The state transitions from Free to Exclusive, and the function returns.
- If Exclusive: The calling thread blocks—spinning briefly then sleeping—until the current owner releases the mutex. Once the mutex becomes Free, the waiting thread acquires it, returning the state to Exclusive.
This blocking behavior is implemented in absl/synchronization/mutex.cc for the slow path, while fast-path operations are inlined in absl/synchronization/mutex.h. The design enforces that only one thread may hold the exclusive lock at any time.
Unlock Operation State Transitions (unlock() / Unlock())
The unlock() (or Unlock()) operation performs a single valid transition:
- Exclusive → Free: The owning thread releases the mutex, allowing waiters to contend for acquisition.
Preconditions are strict:
- The calling thread must be the current owner. Thread identity is encoded in the mutex state.
- The mutex must be in the Exclusive state. Calling
unlock()on a Free mutex is invalid.
These constraints are documented in the header comments at lines 178–224 of absl/synchronization/mutex.h. The implementation uses atomic operations to ensure the transition is visible to all waiting threads immediately.
Non-Reentrancy and Thread Ownership
absl::Mutex is explicitly non-reentrant (non-recursive). A thread that currently holds the Exclusive lock cannot call lock() again without causing undefined behavior. This design prevents accidental deadlock and encourages clear ownership semantics.
Thread identity is integral to the state machine—only the specific thread that transitioned the mutex from Free to Exclusive may perform the subsequent transition back to Free. This ownership tracking enables debug-mode checks that validate correct pairing of lock and unlock calls.
Implementation Files and State Machine Definition
The state machine logic is distributed across three key files in the abseil/abseil-cpp repository:
absl/synchronization/mutex.h: Defines theMutexclass, state constants, and inline fast paths (see lines 178–224 for the state machine documentation).absl/synchronization/mutex.cc: Implements slow-path blocking logic, wait queues, and kernel synchronization primitives.absl/synchronization/mutex_test.cc: Unit tests validating state transitions, contention scenarios, and invalid-operation detection.
The header file explicitly documents the two-state model with a state transition table and comments explaining the Free and Exclusive states, serving as the authoritative reference for the mutex lifecycle.
Code Examples: Valid and Invalid State Transitions
Basic Exclusive Lock Pattern
The following demonstrates the valid Free→Exclusive→Free cycle:
#include "absl/synchronization/mutex.h"
void CriticalSection(absl::Mutex& mu) {
mu.lock(); // Transitions Free → Exclusive
// Critical section execution...
mu.unlock(); // Transitions Exclusive → Free
}
RAII Wrapper (Recommended)
Using absl::MutexLock ensures automatic state restoration via RAII:
#include "absl/synchronization/mutex.h"
void SafeCriticalSection(absl::Mutex& mu) {
absl::MutexLock lock(&mu); // Acquires: Free → Exclusive
// Critical section...
} // Destructor releases: Exclusive → Free
Invalid Operations (Undefined Behavior)
These patterns violate the state machine and will abort debug builds:
#include "absl/synchronization/mutex.h"
void InvalidOperations(absl::Mutex& mu) {
// ERROR: Unlocking a free mutex
mu.unlock(); // Undefined behavior - debug builds crash here
// ERROR: Reentrant lock attempt (if mu already held)
mu.lock(); // First acquisition: Free → Exclusive
mu.lock(); // Invalid - cannot lock while already Exclusive
}
Summary
absl::Muteximplements a two-state machine (Free and Exclusive) for exclusive locks.lock()transitions Free→Exclusive, blocking if the mutex is already held.unlock()transitions Exclusive→Free and must be called only by the owning thread.- The mutex is non-reentrant; recursive locking by the owner is prohibited.
- Invalid state transitions trigger undefined behavior, typically caught by debug assertions in
absl/synchronization/mutex.h. - Use
absl::MutexLockRAII wrapper to ensure correct state transitions and prevent leaks.
Frequently Asked Questions
What happens if I call unlock() on an absl::Mutex that is already Free?
Calling unlock() on a Free mutex is invalid and results in undefined behavior. In debug builds, the implementation detects this state violation and terminates the program with an assertion failure. The state machine requires that unlock() only be called by the thread currently in the Exclusive state.
Can the same thread lock an absl::Mutex multiple times (reentrancy)?
No, absl::Mutex is non-reentrant. If a thread attempts to call lock() while already holding the mutex in the Exclusive state, the operation violates the state machine rules and causes undefined behavior. For recursive locking needs, use absl::ReentrantMutex or refactor to avoid nested lock acquisition.
Where is the state machine documented in the Abseil source code?
The state machine documentation and transition table are located in absl/synchronization/mutex.h at lines 178–224. This section defines the Free and Exclusive states, valid transitions, and preconditions for lock and unlock operations. The implementation details for blocking and wakeups are in absl/synchronization/mutex.cc.
Is the absl::MutexLock RAII class safer than manual lock/unlock calls?
Yes, absl::MutexLock eliminates the risk of missed unlock() calls (which would leave the mutex stuck in the Exclusive state) by automatically calling unlock() in its destructor. This ensures the Exclusive→Free transition occurs even if exceptions are thrown or early returns bypass the manual unlock call, strictly adhering to the state machine lifecycle.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →