nlib
misc/threading/condvar/condvar.cpp

Sample that uses condition variables.

Conditional variables generally use certain combinations of the following values.

Follow the steps below to wait to be notified from other threads.

  1. Apply a lock.
  2. Pass a lock and wait. The lock is released internally.
  3. When notified, the lock is automatically reacquired. Check the state (to account for waiting after notification), and execute if possible.
  4. Change the state to non-signal (when appropriate).

Follow the steps below to notify other threads.

  1. Apply a lock.
  2. Change to the signal state.
  3. Call nn::nlib::threading::CondVar::Notify or nn::nlib::threading::CondVar::NotifyAll() to give notification to other threads.
  4. Unlock.

This is written in the ThreadParam::Exec function.

The even and odd threads signal each other to continue the process in this sample.

using nlib_ns::threading::ScopedLock;
using nlib_ns::threading::UniqueLock;
using nlib_ns::threading::CondVar;
using nlib_ns::threading::SimpleCriticalSection;
using nlib_ns::threading::Thread;
const int NUM_THREADS = 6; // NUM_THREADS must be even
Thread g_Th[NUM_THREADS];
struct CondVarState {
CondVar cond;
SimpleCriticalSection lock;
bool flag;
public:
CondVarState() : flag(false) {}
} g_Cond1, g_Cond2;
class ThreadParam {
public:
ThreadParam() : thread_num_(0), is_success_(false), wait_(NULL), signal_(NULL) {}
void Initialize(int num, CondVarState* w, CondVarState* s) {
thread_num_ = num;
wait_ = w;
signal_ = s;
is_success_ = true;
}
void Exec();
static void ThreadFunc(ThreadParam* param) { param->Exec(); }
bool IsSuccessful() { return is_success_; }
private:
int thread_num_;
bool is_success_;
CondVarState* wait_;
CondVarState* signal_;
};
void ThreadParam::Exec() {
{
UniqueLock<SimpleCriticalSection> lock(wait_->lock);
// Note that the thread is already notified at this point.
while (!wait_->flag) {
nlib_printf(" Thread %d waiting\n", thread_num_);
// releases 'lock' and waits notification
errno_t e = wait_->cond.Wait(lock);
if (nlib_is_error(e)) {
is_success_ = false;
return;
}
// 'lock' is acquired when it returns from Wait(lock)
nlib_printf(" Thread %d may be notified and acquires lock\n", thread_num_);
}
nlib_printf(" Thread %d is notified, and do its own job\n", thread_num_);
wait_->flag = false; // turn off the flag, note that 'lock' is acquired
}
{
ScopedLock<SimpleCriticalSection> lock(signal_->lock);
// you have to set signal_->flag within the criticalsection
signal_->flag = true;
nlib_printf(" Thread %d notifies\n", thread_num_);
signal_->cond.NotifyAll();
}
}
bool CondVarDemo() {
ThreadParam param[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; ++i) {
if (i % 2 == 0) {
param[i].Initialize(i, &g_Cond1, &g_Cond2);
e = g_Th[i].Start(ThreadParam::ThreadFunc, &param[i]);
} else {
param[i].Initialize(i, &g_Cond2, &g_Cond1);
e = g_Th[i].Start(ThreadParam::ThreadFunc, &param[i]);
}
if (nlib_is_error(e)) return false;
}
g_Cond1.lock.lock();
nlib_printf(" Main thread notifies\n");
// At this point, the threads may have already started to wait or not.
g_Cond1.flag = true;
g_Cond1.cond.NotifyAll();
g_Cond1.lock.unlock();
for (int i = 0; i < NUM_THREADS; ++i) {
e = g_Th[i].Join();
if (nlib_is_error(e)) return false;
}
for (int i = 0; i < NUM_THREADS; ++i) {
if (!param[i].IsSuccessful()) return false;
}
return true;
}
static bool SampleMain(int, char**) { return CondVarDemo(); }
NLIB_MAINFUNC