Condition Variables

Name

cyg_cond_init, cyg_cond_destroy, cyg_cond_wait, cyg_cond_timed_wait, cyg_cond_signal, cyg_cond_broadcast -- Synchronization primitive

Synopsis

#include <cyg/kernel/kapi.h>
        

void cyg_cond_init(cyg_cond_t* cond, cyg_mutex_t* mutex);

void cyg_cond_destroy(cyg_cond_t* cond);

cyg_bool_t cyg_cond_wait(cyg_cond_t* cond);

cyg_bool_t cyg_cond_timed_wait(cyg_cond_t* cond, cyg_tick_count_t abstime);

void cyg_cond_signal(cyg_cond_t* cond);

void cyg_cond_broadcast(cyg_cond_t* cond);

Description

Condition variables are used in conjunction with mutexes to implement long-term waits for some condition to become true. For example consider a set of functions that control access to a pool of resources:


cyg_mutex_t res_lock;
res_t res_pool[RES_MAX];
int res_count = RES_MAX;

void res_init(void)
{
    cyg_mutex_init(&res_lock);
    <fill pool with resources>
}

res_t res_allocate(void)
{
    res_t res;

    cyg_mutex_lock(&res_lock);               // lock the mutex

    if( res_count == 0 )                     // check for free resource
        res = RES_NONE;                      // return RES_NONE if none
    else
    {
        res_count--;                         // allocate a resources
        res = res_pool[res_count];
    }

    cyg_mutex_unlock(&res_lock);             // unlock the mutex

    return res;
}

void res_free(res_t res)
{
    cyg_mutex_lock(&res_lock);               // lock the mutex

    res_pool[res_count] = res;               // free the resource
    res_count++;

    cyg_mutex_unlock(&res_lock);             // unlock the mutex
}
      

These routines use the variable res_count to keep track of the resources available. If there are none then res_allocate returns RES_NONE, which the caller must check for and take appropriate error handling actions.

Now suppose that we do not want to return RES_NONE when there are no resources, but want to wait for one to become available. This is where a condition variable can be used:


cyg_mutex_t res_lock;
cyg_cond_t res_wait;
res_t res_pool[RES_MAX];
int res_count = RES_MAX;

void res_init(void)
{
    cyg_mutex_init(&res_lock);
    cyg_cond_init(&res_wait, &res_lock);
    <fill pool with resources>
}

res_t res_allocate(void)
{
    res_t res;

    cyg_mutex_lock(&res_lock);               // lock the mutex

    while( res_count == 0 )                  // wait for a resources
        cyg_cond_wait(&res_wait);

    res_count--;                             // allocate a resource
    res = res_pool[res_count];

    cyg_mutex_unlock(&res_lock);             // unlock the mutex

    return res;
}

void res_free(res_t res)
{
    cyg_mutex_lock(&res_lock);               // lock the mutex

    res_pool[res_count] = res;               // free the resource
    res_count++;

    cyg_cond_signal(&res_wait);              // wake up any waiting allocators

    cyg_mutex_unlock(&res_lock);             // unlock the mutex
}
      

In this version of the code, when res_allocate detects that there are no resources it calls cyg_cond_wait. This does two things: it unlocks the mutex, and puts the calling thread to sleep on the condition variable. When res_free is eventually called, it puts a resource back into the pool and calls cyg_cond_signal to wake up any thread waiting on the condition variable. When the waiting thread eventually gets to run again, it will re-lock the mutex before returning from cyg_cond_wait.

There are two important things to note about the way in which this code works. The first is that the mutex unlock and wait in cyg_cond_wait are atomic: no other thread can run between the unlock and the wait. If this were not the case then a call to res_free by that thread would release the resource but the call to cyg_cond_signal would be lost, and the first thread would end up waiting when there were resources available.

The second feature is that the call to cyg_cond_wait is in a while loop and not a simple if statement. This is because of the need to re-lock the mutex in cyg_cond_wait when the signalled thread reawakens. If there are other threads already queued to claim the lock then this thread must wait. Depending on the scheduler and the queue order, many other threads may have entered the critical section before this one gets to run. So the condition that it was waiting for may have been rendered false. Using a loop around all condition variable wait operations is the only way to guarantee that the condition being waited for is still true after waiting.

Before a condition variable can be used it must be initialized with a call to cyg_cond_init. This requires two arguments, memory for the data structure and a pointer to an existing mutex. This mutex will not be initialized by cyg_cond_init, instead a separate call to cyg_mutex_init is required. If a condition variable is no longer required and there are no threads waiting on it then cyg_cond_destroy can be used.

When a thread needs to wait for a condition to be satisfied it can call cyg_cond_wait. The thread must have already locked the mutex that was specified in the cyg_cond_init call. This mutex will be unlocked and the current thread will be suspended in an atomic operation. When some other thread performs a signal or broadcast operation the current thread will be woken up and automatically reclaim ownership of the mutex again, allowing it to examine global state and determine whether or not the condition is now satisfied.

The kernel supplies a variant of this function, cyg_cond_timed_wait, which can be used to wait on the condition variable or until some number of clock ticks have occurred. The number of ticks is specified as an absolute, not relative tick count, and so in order to wait for a relative number of ticks, the return value of the cyg_current_time() function should be added to determine the absolute number of ticks. The mutex will always be reclaimed before cyg_cond_timed_wait returns, regardless of whether it was a result of a signal operation or a timeout.

There is no cyg_cond_trywait function because this would not serve any purpose. If a thread has locked the mutex and determined that the condition is satisfied, it can just release the mutex and return. There is no need to perform any operation on the condition variable.

When a thread changes shared state that may affect some other thread blocked on a condition variable, it should call either cyg_cond_signal or cyg_cond_broadcast. These calls do not require ownership of the mutex, but usually the mutex will have been claimed before updating the shared state. A signal operation only wakes up the first thread that is waiting on the condition variable, while a broadcast wakes up all the threads. If there are no threads waiting on the condition variable at the time, then the signal or broadcast will have no effect: past signals are not counted up or remembered in any way. Typically a signal should be used when all threads will check the same condition and at most one thread can continue running. A broadcast should be used if threads check slightly different conditions, or if the change to the global state might allow multiple threads to proceed.

Valid contexts

cyg_cond_init is typically called during system initialization but may also be called in thread context. The same applies to cyg_cond_delete. cyg_cond_wait and cyg_cond_timedwait may only be called from thread context since they may block. cyg_cond_signal and cyg_cond_broadcast may be called from thread or DSR context.