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.
2024-03-18 | Open Publication License |