# How Abseil’s Mutex Implements Cooperative vs Non‑Cooperative Scheduling Modes

> Explore Abseil's Mutex cooperative vs non-cooperative scheduling modes. Learn how waiting threads yield to user schedulers or block on kernel primitives for optimized concurrency.

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

---

**Abseil’s `absl::Mutex` distinguishes between cooperative and non‑cooperative scheduling modes to determine whether waiting threads yield to user‑level schedulers or block directly on kernel synchronization primitives.**

The `absl::Mutex` synchronization primitive in the abseil/abseil-cpp repository provides a hybrid threading model that unifies traditional OS thread blocking with user‑level cooperative multitasking. Understanding how Abseil Mutex cooperative vs non‑cooperative scheduling modes work is essential for low‑level library development and high‑performance computing where fibers or coroutines interact with standard mutexes.

## Scheduling Mode Fundamentals

At the core of this implementation lies the `SchedulingMode` enum defined in [`absl/base/internal/scheduling_mode.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/internal/scheduling_mode.h) (lines 30–41). This enumeration provides two distinct values that control thread rescheduling behavior during mutex contention:

- **`SCHEDULE_KERNEL_ONLY`** (value 0): Forces the thread to block exclusively on host‑OS primitives. The waiting thread enters a kernel‑space futex or equivalent wait state without yielding to user‑level schedulers.
- **`SCHEDULE_COOPERATIVE_AND_KERNEL`**: Allows the waiting thread to be rescheduled by Abseil’s cooperative scheduling facilities. While waiting, the thread may yield execution to other cooperative fibers or coroutines before the kernel wakes it.

The mode is stored per‑thread within the `base_internal::ThreadIdentity` structure, enabling each thread to carry its scheduling preference independently of the mutex object itself.

## Core Implementation Components

The mutex delegates its waiting logic to three low‑level subsystems that abstract the cooperative vs. non‑cooperative distinction.

### PerThreadSem Abstraction

The `PerThreadSem` class in [`absl/synchronization/internal/per_thread_sem.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/synchronization/internal/per_thread_sem.h) provides a counting‑semaphore‑like primitive for individual threads. Its static `Wait(KernelTimeout t)` method drives the actual blocking operation:

```cpp
class PerThreadSem {
  // ...
  static inline bool Wait(KernelTimeout t);  // Blocks until count > 0
};

```

Under the hood, `Wait()` invokes the C symbol `AbslInternalPerThreadSemWait`. On POSIX systems, this translates to a futex wait for non‑cooperative modes, while cooperative platforms may instead call `absl::base::scheduling::Yield` to surrender control to the cooperative scheduler.

### Low‑Level Scheduling Helpers

The [`absl/base/internal/low_level_scheduling.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/internal/low_level_scheduling.h) header exposes functions that query and manipulate the current thread’s scheduling capability. The `SchedulingModeIsCooperative()` function returns true only when the calling thread permits cooperative rescheduling. Additionally, the `SchedulingGuard` RAII wrapper allows temporary mode changes by modifying the thread’s `ThreadIdentity` scheduling state.

### Mutex State Encoding

Internally, the mutex state (`mu_`) encodes both the lock ownership status and the scheduling mode for queued waiters. According to comments in [`spinlock.h`](https://github.com/abseil/abseil-cpp/blob/main/spinlock.h) (line 289), bit 1 of the internal state specifically flags whether the lock uses cooperative scheduling. This ensures that all waiters in the queue adhere to the same scheduling discipline as the first blocking thread.

## The Lock Acquisition Path

When a thread calls `Mutex::Lock()` or `Mutex::LockShared()`, the implementation follows a two‑phase approach.

### Fast Path

The fast path attempts atomic acquisition using compare‑and‑swap operations. If the mutex is uncontended, the thread acquires the lock immediately without involving scheduling modes or kernel primitives.

### Slow Path and LockSlowLoop

If contention exists, control transitions to `LockSlowLoop` in `absl/synchronization/mutex.cc`. Within this slow path, the implementation performs the following steps:

1. **Mode Inspection**: The current thread’s scheduling mode is retrieved from `base_internal::ThreadIdentity`.
2. **Cooperative Wait**: If the mode is `SCHEDULE_COOPERATIVE_AND_KERNEL`, the thread invokes `PerThreadSem::Wait()` with a timeout that permits cooperative yielding. This allows other fibers to execute while the mutex remains held.
3. **Non‑Cooperative Wait**: If the mode is `SCHEDULE_KERNEL_ONLY`, the same `PerThreadSem::Wait()` call executes, but the underlying `AbslInternalPerThreadSemWait` uses a pure kernel wait (e.g., futex) that suspends the thread without yielding to the cooperative scheduler.

This hybrid design ensures that the kernel always provides the ultimate contention primitive, while the cooperative layer merely determines whether the thread may be descheduled during the wait.

## Practical Usage Examples

The scheduling mode is not passed as a parameter to `Mutex` methods; instead, it is derived from the thread’s current execution context.

### Cooperative Mode with Fibers

When running on a cooperative scheduler (such as Abseil’s fibers), threads automatically inherit the cooperative scheduling mode:

```cpp
#include "absl/synchronization/mutex.h"

void CooperativeWorker() {
  // Running on a fiber; thread identity automatically marks this as cooperative
  absl::Mutex mu;
  
  mu.Lock();  // May yield to other fibers while waiting
  // Critical section
  mu.Unlock();
}

```

In this scenario, `mu.Lock()` may invoke `absl::base::scheduling::Yield` via `PerThreadSem::Wait()`, allowing other cooperative threads to make progress before the kernel wakes this waiter.

### Non‑Cooperative Mode with SchedulingGuard

For plain OS threads or low‑level scheduler implementations, explicitly disable cooperative rescheduling using `SchedulingGuard`:

```cpp
#include "absl/synchronization/mutex.h"
#include "absl/base/internal/scheduling_mode.h"

void NonCooperativeWorker() {
  // Force kernel‑only scheduling for this scope
  absl::base_internal::SchedulingGuard guard(
      absl::base_internal::SchedulingMode::SCHEDULE_KERNEL_ONLY);
  
  absl::Mutex mu;
  mu.Lock();  // Blocks on futex; no cooperative yielding
  // Critical section
  mu.Unlock();
}

```

This pattern ensures strict kernel‑only semantics, preventing the cooperative scheduler from interfering with the thread’s wait state.

## Safety Constraints and Design Guarantees

The implementation enforces strict safety invariants regarding mode nesting. As documented in [`absl/base/internal/scheduling_mode.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/base/internal/scheduling_mode.h) (lines 46–48), non‑cooperative resources must never be nested beneath cooperative ones. Attempting to acquire a cooperative mutex while holding a non‑cooperative lock constitutes a programming error that triggers an abort in debug builds. This constraint prevents deadlocks where a cooperative scheduler might attempt to reschedule a thread that holds a kernel‑only resource.

## Summary

- **Abseil’s `absl::Mutex`** supports two scheduling modes: `SCHEDULE_COOPERATIVE_AND_KERNEL` and `SCHEDULE_KERNEL_ONLY`.
- **PerThreadSem** abstracts the wait mechanism, choosing between cooperative yielding and kernel futex waits based on the thread’s current mode.
- **LockSlowLoop** in `mutex.cc` inspects the thread’s `ThreadIdentity` to select the appropriate blocking strategy during contention.
- **SchedulingGuard** provides RAII semantics for temporarily forcing non‑cooperative behavior on cooperative threads.
- **Safety invariants** prohibit nesting cooperative locks under non‑cooperative ones to prevent scheduler deadlocks.

## Frequently Asked Questions

### What is the default scheduling mode for absl::Mutex?

By default, `absl::Mutex` derives the scheduling mode from the calling thread’s `ThreadIdentity`. Threads running on standard OS threads without an active cooperative scheduler typically default to `SCHEDULE_KERNEL_ONLY`, while threads managed by Abseil’s fiber or cooperative scheduling infrastructure automatically use `SCHEDULE_COOPERATIVE_AND_KERNEL`.

### Can I mix cooperative and non-cooperative mutexes in the same program?

Yes, provided you respect the nesting constraints. You may acquire a non‑cooperative mutex while holding a cooperative one, but the reverse is forbidden. Non‑cooperative resources must never be held when attempting to acquire cooperative resources to avoid violating scheduler assumptions.

### How does Abseil implement the actual wait operation on Linux?

On Linux, the `PerThreadSem::Wait()` function calls `AbslInternalPerThreadSemWait`, which uses the futex system call (`FUTEX_WAIT`) for non‑cooperative waits. When cooperative mode is active, the implementation may instead call `absl::base::scheduling::Yield` to surrender control to the user‑level scheduler before falling back to the futex if necessary.

### What is the performance impact of cooperative scheduling mode?

Cooperative mode adds minimal overhead when the mutex is uncontended (fast path). Under contention, cooperative mode may reduce latency for fiber‑based workloads by allowing other cooperative threads to execute during the wait, though it introduces a slight indirection through the `PerThreadSem` abstraction compared to raw futex operations.