nlib
misc/threading/condvar/condvar.cpp

条件変数の利用を行うサンプルです。

条件変数は一般に、以下を組み合わせて利用します。

他のスレッドからの通知を待つ場合は、以下の手順で行います。

  1. ロックをかける
  2. ロックを渡してウェイトする。このとき内部でロックが解除されます。
  3. 通知を受けた場合は(自動的にロックが再取得され)、状態をチェックして(通知後にウェイトする場合を考慮)、処理が実行可能ならば実行する。
  4. 状態を非通知に変更する(そうするのが適切な場合)

他のスレッドに通知を行う場合は、以下の手順で行います。

  1. ロックをかける。
  2. 状態を通知に変更する。
  3. nn::nlib::threading::CondVar::Notify(), nn::nlib::threading::CondVar::NotifyAll()で他のスレッドに通知する。
  4. ロックを解除する。

これらがThreadParam::Exec()関数に記述されています。

サンプルでは奇数番目のスレッドと偶数番目のスレッドがお互いに通知しあって処理が進んでいきます。

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