# absl::Mutex Internal State Machine: Lock/Unlock State Transitions Explained

> Understand the absl::Mutex internal state machine. Learn how lock and unlock operations transition between Free and Exclusive states and avoid undefined behavior.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: internals
- Published: 2026-07-18

---

**`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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/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`](https://github.com/abseil/abseil-cpp/blob/main/absl/synchronization/mutex.h)**: Defines the `Mutex` class, 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:

```cpp
#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:

```cpp
#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:

```cpp
#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::Mutex`** implements 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`](https://github.com/abseil/abseil-cpp/blob/main/absl/synchronization/mutex.h).
- Use **`absl::MutexLock`** RAII 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`](https://github.com/abseil/abseil-cpp/blob/main/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.