Skip to content

Condition Variables

Rich Neswold edited this page Apr 1, 2022 · 2 revisions

Condition variables are used with mutexes and are a standard form of thread synchronization. They allow threads to test -- and possibly wait -- for a condition to occur, atomically. If you want to synchronize information between a task and an interrupt routine, use VWPP::Event instead.

NOTE: Starting with v2.7, we introduced a nested, version namespace to enforce matching APIs when building and deploying. In the following code examples, we refer to the namespace as "VWPP". This translates to vwpp for pre-2.7 libraries and to vwpp::v2_7 for v2.7.

The VWPP::CondVar<> API

This API consists of two classes, VWPP::CondVar<> and VWPP::PMCondVar<>. VWPP::CondVar<> is for using mutexes that are in global scope and VWPP::PMCondVar<> is for using mutexes contained in an object. Examples are given below.


Constructor

VWPP::CondVar<VWPP::Mutex&>()

Creates an CondVar<> object associated with the mutex specified in the template argument. If the object can't be created, a std::bad_alloc exception will be thrown.


bool VWPP::CondVar<mtx>::wait(VWPP::Lock<mtx> const&, int = WAIT_FOREVER)

A task calling this method must provide a lock on the associated mutex. The task will release the mutex and block atomically. When another task signals the condition variable, the blocked task will regain ownership of the mutex before returning. The second parameter indicates a timeout, given in milliseconds. No parameter needs to be provided if the default -- wait forever -- is sufficient.

If the wait() timeout expires, the method returns false. If the task is awakened due to the event getting signalled, the method returns true. This method cannot be called from within an interrupt routine because interrupts cannot block.


void VWPP::CondVar<mtx>::signal(VWPP::Lock<mtx> const&)

Wakes one task pending on the condition variable. If multiple tasks are pending, only the highest priority task runs. If no tasks are pending on the event when this method is called, the first task to wait for the event will not block. The only parameter is a lock on the mutex.


Constructor

VWPP::PMCondVar<T, pmtx>()

Creates an PMCondVar<> object associated with the mutex contained in class T. If the object can't be created, a std::bad_alloc exception will be thrown.


bool VWPP::PMCondVar<T, pmtx>::wait(VWPP::PMLock<T, pmtx> const&, int = WAIT_FOREVER)

A task calling this method must provide a lock on the associated mutex. The task will release the mutex and block atomically. When another task signals the condition variable, the blocked task will regain ownership of the mutex before returning. The second parameter indicates a timeout, given in milliseconds. No parameter needs to be provided if the default -- wait forever -- is sufficient.

If the wait() timeout expires, the method returns false. If the task is awakened due to the event getting signalled, the method returns true. This method cannot be called from within an interrupt routine because interrupts cannot block.


void VWPP::PMCondVar<T, pmtx>::signal(VWPP::PMLock<T, pmtx> const&)

Wakes one task pending on the condition variable. If multiple tasks are pending, only the highest priority task runs. If no tasks are pending on the event when this method is called, the first task to wait for the event will not block. The only parameter is a lock on the mutex.

Examples

In this example, we use a condition variable to signal when a linked list has an element. The first step is to define the global resources (since these are being shared between threads, they need to be global.)

VWPP::Mutex mtx;
VWPP::CondVar<mtx> cv;
std::list<int> list;

Next we write the function that inserts an element into the list.

void push(int val)
{
    VWPP::Mutex::Lock<mtx> lock;

    list.push_back(val);
    cv.signal(lock);
}

Finally, we write a function that blocks until an item is in the list.

int pop()
{
    VWPP::Mutex::Lock<mtx> lock;

    while (list.empty())
        cv.wait(lock);

    int const tmp = list.front();

    list.pop_front();
    return tmp;
}

Let's instead create a list that has built-in synchronization (to see how PMCondVar<> is used):

template <class T>
class sync_list {
    VWPP::Mutex mtx;
    VWPP::PMCondVar<sync_list, VWPP::Mutex sync_list::*pmtx> cv;

    typedef VWPP::PMLock<sync_list, VWPP::Mutex sync_list::*pmtx> ObjLock;

    std::list<T> list;

 public:

    void push(T const& val)
    {
        ObjLock lock(this);

        list.push_back(val);
        cv.signal(lock);
    }

    T pop()
    {
        ObjLock lock(this);

        while (list.empty())
            cv.wait(this, lock);

        T const tmp = list.front();

        list.pop_front();
        return tmp;
    }
};

We can create a synchronized list of integers:

sync_list<int> lst;

lst.push(1);

int v1 = lst.pop();    // v1 is set to 1
int v2 = lst.pop();    // the task blocks until an entry is added to the list

Clone this wiki locally