# How absl::call_once Ensures Thread-Safe One-Time Initialization in C++

> Learn how absl call_once ensures thread-safe one-time initialization in C++ using a lock-free state machine and atomic control word for guaranteed single execution across concurrent threads.

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

---

**`absl::call_once` implements a lock-free state machine using a 32-bit atomic control word that transitions through four distinct states—uninitialized, running, waiting, and done—to guarantee a callable executes exactly once across concurrent threads.**

The Abseil C++ library provides `absl::call_once` in [[`absl/base/call_once.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/call_once.h)](https://github.com/abseil/abseil-cpp/blob/master/absl/base/call_once.h) as a high-performance alternative to `std::call_once`, eliminating lock contention through a carefully designed wait-free fast path and an efficient spin-lock fallback mechanism.

## The Atomic State Machine Behind absl::call_once

At the core of `absl::call_once` lies a state machine encoded in a single `std::atomic<uint32_t>` control word. This design allows multiple threads to coordinate initialization without allocating mutexes or kernel objects.

### The once_flag Control Word

Each `absl::once_flag` embeds a `std::atomic<uint32_t> control_` member initialized to `kOnceInit` (value `0`). The flag is non-copyable and non-movable, ensuring that the control word's address remains stable for the program's lifetime. Because the initial state is zero, global or static `once_flag` objects require no runtime initialization.

### Four States of Initialization

The control word cycles through four distinct values representing the lifecycle of the one-time operation:

| State | Value | Description |
|-------|-------|-------------|
| **kOnceInit** | `0` | Initialization has not started |
| **kOnceRunning** | `0x65C2937B` | A thread is currently executing the user function |
| **kOnceWaiter** | `0x05A308D2` | One or more threads are waiting for completion |
| **kOnceDone** | `221` | The function completed successfully |

The unusual hex constants for `kOnceRunning` and `kOnceWaiter` serve as debugging aids while keeping the initial state zero-initialized.

## The Lock-Free Algorithm Step-by-Step

The implementation in [`absl/base/call_once.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/call_once.h) uses a hybrid approach: a lock-free fast path for the common case, followed by a spin-lock protocol for contended scenarios.

### Fast-Path with Acquire Semantics

When `absl::call_once` is invoked, it first loads the control word using `memory_order_acquire`. If the value equals `kOnceDone`, the function returns immediately without synchronization overhead. This makes repeated calls to `call_once` essentially free after initialization completes.

```cpp
// Simplified conceptual flow
if (flag.control_.load(std::memory_order_acquire) == kOnceDone) {
  return;  // Already initialized
}

```

### CAS Transition to Running State

If the fast-path fails, the thread attempts to atomically replace `kOnceInit` with `kOnceRunning` using `compare_exchange_strong`. The thread that succeeds becomes the *owner* and proceeds to execute the user callable. All other threads fail the CAS and enter the waiting protocol.

```cpp
uint32_t expected = kOnceInit;
if (flag.control_.compare_exchange_strong(expected, kOnceRunning,
                                          std::memory_order_acq_rel)) {
  // This thread is the owner; run the initialization
} else {
  // Another thread owns initialization; wait
}

```

### Spin-Lock Waiting for Completion

Threads that fail to acquire the running state invoke `base_internal::SpinLockWait`, defined in [[`absl/base/internal/spinlock_wait.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/internal/spinlock_wait.h)](https://github.com/abseil/abseil-cpp/blob/master/absl/base/internal/spinlock_wait.h). This routine uses a transition table to determine behavior based on the current state:

```cpp
static const base_internal::SpinLockWaitTransition trans[] = {
    {kOnceInit,    kOnceRunning, true},
    {kOnceRunning, kOnceWaiter,  false},
    {kOnceDone,    kOnceDone,    true}
};

```

The wait logic operates as follows:

- **If the word is `kOnceInit`**: The thread retries the CAS (another thread may have completed between the initial check and the wait).
- **If the word is `kOnceRunning`**: The thread atomically changes the state to `kOnceWaiter` and parks.
- **When the owner finishes**: It writes `kOnceDone` with release semantics and wakes waiters via `SpinLockWake`.

## Exception Safety and Scheduling Considerations

Beyond the core state machine, `absl::call_once` handles edge cases critical to robust C++ applications.

### Exception Handling Behavior

If the user-provided callable throws an exception, the control word remains in the `kOnceRunning` state. This design ensures that subsequent calls to `call_once` will retry the initialization, preventing a failed initialization from permanently blocking the system. Only successful completion stores `kOnceDone`.

### Cooperative Scheduling Support

`absl::call_once` supports cooperative multitasking environments through `SchedulingHelper`. When invoked with `SCHEDULE_KERNEL_ONLY` mode, the implementation temporarily disables kernel-only rescheduling to prevent deadlocks in cooperative scheduler contexts. This logic resides in [[`absl/base/internal/low_level_scheduling.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/internal/low_level_scheduling.h)](https://github.com/abseil/abseil-cpp/blob/master/absl/base/internal/low_level_scheduling.h).

## Low-Level Variants and Implementation Location

For internal scheduler initialization where standard scheduling modes might recurse, Abseil provides `absl::base_internal::LowLevelCallOnce`. This variant forces `SCHEDULE_KERNEL_ONLY` mode but otherwise implements the identical state machine algorithm.

All public APIs and implementations reside in the header file:

- **[[`absl/base/call_once.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/call_once.h)](https://github.com/abseil/abseil-cpp/blob/master/absl/base/call_once.h)** – Contains `absl::call_once`, `absl::once_flag`, and the full inline implementation
- **[[`absl/base/internal/spinlock_wait.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/internal/spinlock_wait.h)](https://github.com/abseil/abseil-cpp/blob/master/absl/base/internal/spinlock_wait.h)** – Provides the `SpinLockWait`/`SpinLockWake` primitives
- **[[`absl/base/internal/scheduling_mode.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/internal/scheduling_mode.h)](https://github.com/abseil/abseil-cpp/blob/master/absl/base/internal/scheduling_mode.h)** – Defines `SCHEDULE_COOPERATIVE_AND_KERNEL` and `SCHEDULE_KERNEL_ONLY`

## Practical Usage Example

The following example demonstrates thread-safe singleton initialization using `absl::call_once`:

```cpp
#include "absl/base/call_once.h"
#include <iostream>
#include <thread>

absl::once_flag init_flag;

void Initialize() {
  // Expensive one-time work (e.g., initializing a singleton)
  std::cout << "Running initialization on thread "
            << std::this_thread::get_id() << '\n';
}

// Each thread calls this; the body runs only once.
void Worker() {
  absl::call_once(init_flag, Initialize);
  std::cout << "Worker thread continues: " << std::this_thread::get_id()
            << '\n';
}

int main() {
  std::thread t1(Worker);
  std::thread t2(Worker);
  std::thread t3(Worker);
  t1.join(); t2.join(); t3.join();
}

```

*Output (order may vary, but “Running initialization …” appears exactly once):*

```

Running initialization on thread 140735578455040
Worker thread continues: 140735578455040
Worker thread continues: 140735570062336
Worker thread continues: 140735561669632

```

For rare cases requiring early scheduler initialization, use the low-level variant:

```cpp
absl::once_flag low_flag;
absl::base_internal::LowLevelCallOnce(&low_flag, []{
    // Initialization that must run before any kernel-only scheduling.
});

```

## Summary

- **`absl::call_once`** uses a lock-free atomic state machine in [`absl/base/call_once.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/call_once.h) to guarantee single execution.
- The **four-state protocol** (`kOnceInit`, `kOnceRunning`, `kOnceWaiter`, `kOnceDone`) coordinates between owner and waiting threads without kernel mutexes.
- **Fast-path optimization** provides zero-cost overhead for subsequent calls after initialization completes.
- **Exception safety** leaves the flag in a retryable state if the callable throws, preventing permanent initialization failure.
- **Cooperative scheduling support** via `SchedulingHelper` ensures correct behavior in kernel-only and hybrid scheduling environments.

## Frequently Asked Questions

### What happens if the callable passed to absl::call_once throws an exception?

If the user callable throws, the control word remains in the `kOnceRunning` state according to the implementation in [`absl/base/call_once.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/call_once.h). This causes subsequent calls to retry the initialization, ensuring the system never assumes completion when an exception occurred. Only a successful return stores `kOnceDone`.

### How is absl::call_once different from std::call_once?

`absl::call_once` uses a custom lock-free state machine with a spin-wait fallback, while `std::call_once` typically relies on implementation-defined mutexes or condition variables. The Abseil version provides a fast-path that returns immediately with `memory_order_acquire` if initialization is complete, and it supports cooperative scheduling modes that standard library implementations may not handle.

### Why does absl::call_once use such unusual hexadecimal values for its states?

The constants `0x65C2937B` for `kOnceRunning` and `0x05A308D2` for `kOnceWaiter` serve as debugging aids. These distinctive values make it easy to identify the current state when inspecting memory or debugging core dumps, while keeping `kOnceInit` as zero allows for zero-initialization of global `once_flag` objects.

### Can I use absl::call_once with cooperative scheduling environments?

Yes. The implementation includes `SchedulingHelper` to handle cooperative scheduling modes defined in [`absl/base/internal/scheduling_mode.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/internal/scheduling_mode.h). When the mode is `SCHEDULE_KERNEL_ONLY`, `absl::call_once` temporarily disables kernel-only rescheduling to prevent deadlocks. For initialization that must run before the scheduler is fully initialized, use `absl::base_internal::LowLevelCallOnce`.