AOSP Expert & Production Engineering
3 min read

Condition Variables

The Condition Variable Pattern: Wait and Notify

A Condition Variable allows threads to synchronize based on the actual value of data, rather than just acquiring a lock. It enables a thread to efficiently sleep until a specific condition becomes true.

Condition variables must always be used in conjunction with a mutex.

The Workflow:

  1. Thread A (The Waiter): Acquires the mutex, checks a condition (e.g., "is the queue empty?"). If the condition is not met, it calls wait() on the condition variable.
    • Crucially, calling wait() atomically releases the mutex and puts the thread to sleep.
  2. Thread B (The Notifier): Acquires the mutex, changes the data (e.g., "adds an item to the queue"), and then calls notify() (or signal()) on the condition variable. It then releases the mutex.
  3. Thread A Awakens: The operating system wakes up Thread A. Before wait() returns, Thread A automatically re-acquires the mutex. It must then re-check the condition.

Spurious Wakeups

A critical concept is the "spurious wakeup". Operating systems are permitted to wake up a thread waiting on a condition variable even if no other thread called notify().

Because of spurious wakeups, the wait() call must always be placed inside a while loop that checks the condition, never a simple if statement.

// Correct pattern: while loop
while (queue.empty()) {
    pthread_cond_wait(&cond_var, &mutex);
}
// Now we hold the mutex AND the queue is definitely not empty

std::condition_variable Usage in AOSP

Modern AOSP C++ code relies heavily on std::condition_variable and std::unique_lock (which allows unlocking and re-locking, unlike lock_guard).

#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;

void consumer() {
    std::unique_lock<std::mutex> lock(mtx);
    
    // The lambda provides the condition. 
    // cv.wait automatically handles the while-loop for spurious wakeups!
    cv.wait(lock, []{ return !data_queue.empty(); });
    
    int data = data_queue.front();
    data_queue.pop();
    // process data...
}

void producer(int new_data) {
    {
        std::lock_guard<std::mutex> lock(mtx);
        data_queue.push(new_data);
    } // lock released here
    
    cv.notify_one(); // Wake up one waiting consumer
    // Use cv.notify_all() to wake up all waiting threads
}

Android Condition Class

In the Android framework's native utility libraries (system/core/libutils/include/utils/Condition.h), you will frequently encounter the android::Condition class. It is a thin wrapper around pthread_cond_t.

It operates similarly to standard condition variables but is often used in tandem with Android's Mutex class.

#include <utils/Condition.h>
#include <utils/Mutex.h>

android::Mutex mLock;
android::Condition mCondition;

// Waiter
mLock.lock();
while (condition_is_false) {
    mCondition.wait(mLock);
}
mLock.unlock();

// Notifier
mCondition.broadcast(); // equivalent to notify_all