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:
- 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.
- Crucially, calling
- Thread B (The Notifier): Acquires the mutex, changes the data (e.g., "adds an item to the queue"), and then calls
notify()(orsignal()) on the condition variable. It then releases the mutex. - 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