C++ Thread Annotations (EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED) and absl::Mutex: A Complete Guide
Abseil's thread annotations are zero-cost macros that enable Clang's static thread-safety analyzer to detect data races, missing locks, and deadlock risks at compile time by explicitly declaring which mutexes must be held when calling functions.
The abseil/abseil-cpp library provides a comprehensive thread-safety annotation system that allows developers to express locking requirements directly in source code. By using macros like EXCLUSIVE_LOCKS_REQUIRED and SHARED_LOCKS_REQUIRED alongside absl::Mutex, you can catch concurrency bugs during compilation without incurring any runtime overhead.
What Are Thread Annotations?
Thread annotations are a set of preprocessor macros defined in absl/base/thread_annotations.h that communicate locking expectations to the Clang static analyzer. These annotations expand to nothing in production builds, ensuring zero runtime cost while providing compile-time verification of thread-safety contracts.
Core Thread Annotation Types
LOCKABLE and SCOPED_LOCKABLE
The LOCKABLE annotation marks a class as a lock type capable of being tracked by the analyzer. In absl/synchronization/mutex.h, absl::Mutex is declared with this attribute. The SCOPED_LOCKABLE annotation identifies RAII lock guards that acquire locks in their constructors and release them in destructors. The absl::MutexLock and absl::ReaderMutexLock classes in absl/synchronization/mutex_lock.h carry this annotation.
EXCLUSIVE_LOCKS_REQUIRED and SHARED_LOCKS_REQUIRED
EXCLUSIVE_LOCKS_REQUIRED(mutex) declares that a function must only be called while holding the specified mutex in exclusive (write) mode. Conversely, SHARED_LOCKS_REQUIRED(mutex) indicates that the function requires the mutex to be held in shared (read) mode. These annotations are defined in absl/base/thread_annotations.h and generate compile-time warnings if callers fail to acquire the appropriate lock type.
LOCKS_EXCLUDED
The LOCKS_EXCLUDED(mutex) annotation specifies that a function must not be called while holding the listed mutex. This prevents potential deadlock scenarios where a function might attempt to acquire a lock already held by the caller.
Lock Acquisition and Release Attributes
EXCLUSIVE_LOCK_FUNCTION(mutex) and SHARED_LOCK_FUNCTION(mutex) annotate functions that acquire locks, while UNLOCK_FUNCTION(mutex) marks functions that release them. These attributes allow the analyzer to track lock state across function boundaries and through complex control flow.
GUARDED_BY and PT_GUARDED_BY
Defined in absl/base/attributes.h, GUARDED_BY(mutex) marks data members that must only be accessed while holding the associated mutex. The PT_GUARDED_BY variant provides similar protection for the data pointed to by a pointer, ensuring thread-safe access to heap-allocated objects.
How Thread Annotations Work with absl::Mutex
The integration between thread annotations and absl::Mutex leverages Clang's -Wthread-safety diagnostics to verify locking discipline at compile time. When you annotate a method with EXCLUSIVE_LOCKS_REQUIRED(mu_), the analyzer checks that every call site has acquired mu_ exclusively—typically via absl::MutexLock—before invoking the method.
If a caller violates this contract, Clang emits a diagnostic such as:
warning: calling function 'Counter::Increment' without holding exclusive lock 'mu_'
Similarly, absl::MutexLock uses SCOPED_LOCKABLE and EXCLUSIVE_LOCK_FUNCTION annotations, allowing the analyzer to understand that the constructor acquires the lock and the destructor releases it. This enables the tool to verify that annotated functions are called within the appropriate scope of a lock guard.
Practical Implementation Example
The following example demonstrates the complete workflow: declaring a mutex, annotating methods with locking requirements, and using RAII guards to satisfy those requirements.
#include "absl/synchronization/mutex.h"
#include "absl/base/thread_annotations.h"
class SharedCounter {
public:
// Must hold `mu_` exclusively before calling.
void Increment() EXCLUSIVE_LOCKS_REQUIRED(mu_) {
++value_;
}
// May hold `mu_` in shared mode.
int Get() SHARED_LOCKS_REQUIRED(mu_) const {
return value_;
}
private:
mutable absl::Mutex mu_; // The lock.
int value_ ABSL_GUARDED_BY(mu_) = 0; // Guarded data.
};
void Example() {
SharedCounter counter;
// Correct usage – lock acquired before calling Increment().
{
absl::MutexLock lock(&counter.mu_);
counter.Increment(); // OK: exclusive lock held.
}
// Incorrect usage – missing lock; Clang will warn.
// counter.Increment(); // Warning: call without exclusive lock.
// Correct shared usage.
{
absl::ReaderMutexLock lock(&counter.mu_);
int v = counter.Get(); // OK: shared lock held.
}
}
In this implementation, the mu_ member is marked as LOCKABLE implicitly through the absl::Mutex type, while value_ uses ABSL_GUARDED_BY(mu_) from absl/base/attributes.h to declare that all accesses must occur under the protection of mu_.
Key Source Files
absl/base/thread_annotations.h: Defines all annotation macros includingEXCLUSIVE_LOCKS_REQUIRED,SHARED_LOCKS_REQUIRED,LOCKS_EXCLUDED, andSCOPED_LOCKABLE.absl/synchronization/mutex.h: Implementsabsl::Mutexwith theLOCKABLEattribute.absl/synchronization/mutex_lock.h: Provides RAII guard classes (absl::MutexLock,absl::ReaderMutexLock) marked asSCOPED_LOCKABLE.absl/base/attributes.h: SuppliesABSL_GUARDED_BYandABSL_PT_GUARDED_BYfor data member protection.
Summary
- Thread annotations are compile-only macros that express locking requirements to Clang's static analyzer.
EXCLUSIVE_LOCKS_REQUIREDenforces exclusive lock ownership, whileSHARED_LOCKS_REQUIREDenforces shared (read) lock ownership before function entry.absl::Mutexintegrates with the annotation system throughLOCKABLEmarkers inabsl/synchronization/mutex.hand RAII guards markedSCOPED_LOCKABLEinabsl/synchronization/mutex_lock.h.GUARDED_BYfromabsl/base/attributes.hprotects data members by requiring mutex acquisition on access.- All annotations impose zero runtime overhead and expand to nothing in production builds.
Frequently Asked Questions
What happens if I call a function without the required lock?
The Clang static analyzer emits a compile-time warning indicating that the function requires an exclusive or shared lock that is not currently held at the call site. For example, calling a method annotated with EXCLUSIVE_LOCKS_REQUIRED(mu_) without holding mu_ exclusively will generate a diagnostic warning.
Do thread annotations add runtime overhead?
No. The macros defined in absl/base/thread_annotations.h expand to nothing during compilation. They are purely compile-time hints for the static analyzer and impose zero runtime cost in production binaries.
Which compiler supports thread annotations?
Thread annotations require Clang with the -Wthread-safety flag enabled. The static analysis is built into Clang's diagnostics framework and is not available in GCC or MSVC. You must compile with Clang to receive thread-safety warnings.
Can I use thread annotations with custom lock types?
Yes. You can apply the LOCKABLE annotation to any class that implements lock/unlock semantics, and SCOPED_LOCKABLE to corresponding RAII guards. This enables the same static analysis for custom synchronization primitives as provided for absl::Mutex.
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 →