How Abseil’s Mutex Implements Cooperative vs Non‑Cooperative Scheduling Modes
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 (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 provides a counting‑semaphore‑like primitive for individual threads. Its static Wait(KernelTimeout t) method drives the actual blocking operation:
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 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 (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:
- Mode Inspection: The current thread’s scheduling mode is retrieved from
base_internal::ThreadIdentity. - Cooperative Wait: If the mode is
SCHEDULE_COOPERATIVE_AND_KERNEL, the thread invokesPerThreadSem::Wait()with a timeout that permits cooperative yielding. This allows other fibers to execute while the mutex remains held. - Non‑Cooperative Wait: If the mode is
SCHEDULE_KERNEL_ONLY, the samePerThreadSem::Wait()call executes, but the underlyingAbslInternalPerThreadSemWaituses 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:
#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:
#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 (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::Mutexsupports two scheduling modes:SCHEDULE_COOPERATIVE_AND_KERNELandSCHEDULE_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.ccinspects the thread’sThreadIdentityto 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.
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 →