Condition

01 Mar 2014

#Condition Variables

Sometimes a thread may need to wait until some other process is completed in order for it to continue. The simplest way to do this would be to run a loop until some condition is met and then continue. For example, if you were implementing a timer that would perform some action at a later date, you might spawn a thread that loops until the current time matches that of the time it should fire. Once the condition is met, it proceeds to the next part of the code. The problem with this is that the thread is taking up CPU cycles performing a loop while the CPU could be giving that time to threads that have actual work to do. This concept is called busy waiting. To prevent our thread from busy waiting, C++ 11’s thread library, like most other thread libraries, has a condition_variable. std::condition_variable will block a thread until a notification is received in such a way that CPU time is saved. To do this, we use the wait member function. For example:

cv.wait(lk); //lk is the lock

There is one thing we should bring up before we proceed and that is the concept of spurious wakeups. With many thread APIs, POSIX and Windows included, a thread can be woken up periodically even through no thread signalled the condition variable. Because of this complication, it is necessary to check to make sure the variable you are testing against is actually correct. Lets say we are testing a bool called ready. Passing in a Lambda function, we can do this easily:

cv.wait(lk, []{return ready;});

Which is equivalent to:

while(!ready)
{
        cv.wait(lk);
}

To analyze a condition variable shared across multiple threads, we will need to acquire a lock, and the wait function will actually take care of the task of unlocking the mutex and suspending the thread until a notification is received to wake it up, at which point the lock will be reacquired.

#include <iostream>
#include <thread>

std::mutex m;
std::condition_variable cv;
bool ready = false;
void _WaitingThread()
{
    std::unique_lock<std::mutex> lk(m);
    std::cout << "Waiting..." << std::endl;
    cv.wait(lk, []{return ready;});
    std::cout << "We can now do more work" << std::endl;
}

void _SignalingThread()
{
    std::lock_guard<std::mutex> lk(m);
    
    //do some work...
    std::chrono::milliseconds sleepDuration(3000);
    std::this_thread::sleep_for(sleepDuration);
    
    ready = true; //we are now ready
    std::cout << "Notifying..." << std::endl;
    cv.notify_one(); //notify sleeping thread we are ready to process
}

void StartThreads()
{
    std::thread t1(_WaitingThread);
    std::thread t2(_SignalingThread);
    t1.detach();
    t2.detach();
}

The notify_one function will notify one thread to wake up. A condition variable can block multiple threads at the same time, and to notify all waiting threads we can use the notify_all() function.

You will notice here we used something called chrono (instead of say, a usleep() function). C++ 11 introduces the chrono library which can be quite helpful in many scenarios. Maybe we want to wait, but if nothing ever happens, we move on. We can wait until a specific time or until a predefined timeout occurs. wait_for() will unblock the thread when a specific amount of time has passed, whereas wait_until() can be used to set a specific date. In either case, if the notification is sent before the timeout, the thread will unblock just as it does with the wait() function. A steady clock is used for the duration. There are many units of time we can use, such as nanoseconds, microseconds, milliseconds, seconds, minutes and hours. Check out the std::chrono library for all the options you can use.

As with other parts of this higher-level thread library, a native_handle() function will return you the underlying handle. For example, on iOS, Mac OS or any POSIX system, this will be a pthread_cond_t * variable. On Windows, it is PCONDITION_VARIABLE.

On a side note, you may come across a time where you need a function to only ever be called once – even across multiple threads - even if two threads call it at the same time. An example might be initialization during a singleton class.

std::once_flag flag;
void SomeSingletonClass::sharedSingleton()
{
    std::cout << "This is called each and every time" << std::endl;

    std::call_once(flag, [](){std::cout << "Init stuff. This is called only once" << std::endl;});
}

The call_once() function utilizes the std::once_flag which coordinates to make sure that a function only gets called once and runs to completion. Any C++ Callable object can be used as the second parameter. Here we decide to pass in a Lambda function.

For more information, check out condition_variable