yohhoyの日記

技術的メモをしていきたい日記

future+promiseでone-shotイベント

C++標準ライブラリの future/promise を利用してone-shotイベントを実現する。

本記事での“one-shotイベント”は、1度しか使えないスレッド間イベント待機/通知機構を指している。環境依存の実装であれば、WaitForSingleObject/SetEvent(Windows API)やsem_wait/sem_post(POSIX)の組で実現可能*1

std::promise(イベント通知側)とstd::shared_future(複数のイベント待機側)を利用したone-shotイベントの実装コード。

#include <thread>
#include <future>

void func(std::shared_future<void> ftr)
{
  //...
  ftr.wait();  // イベント待機
  //...
}

int main()
{
  std::promise<void> prm;
  auto ftr = prm.get_future().share();

  std::thread t1(func, ftr), t2(func, ftr);
  //...
  prm.set_value();  // イベント通知
  //...
  t1.join();  t2.join()
}

shared_future::waitでイベント待機中の2つのスレッドt1, t2に対し、mainスレッドからpromise::set_valueでイベントシグナル通知を行っている。また、イベント待機をshared_future::getで行い、通知側はpromise::set_value(正常時)とset_exception(異常時)で使い分ければ、待機側スレッドへの例外通知を実現できる。

おまけ:mutex+condition_variable版

mutexとcondition_variableを利用したイベントオブジェクトの単純な実装コード。こちらはイベントリセット(resetメンバ関数)を提供するため、イベントオブジェクトの使い回しが可能となる。

#include <mutex>
#include <condition_variable>

class event {
private:
  std::mutex m_;
  std::condition_variable cv_;
  bool flag_;
public:
  explicit event(bool init_flag = false)
    : flag_(init_flag) {}
  event(const event&) = delete;
  event& operator=(const event&) = delete;

  void wait() {
    std::unique_lock<std::mutex> lk(m_);
    cv_.wait(lk, [&]{ return flag_; });
  }

  void set() {
    std::lock_guard<std::mutex> lk(m_);
    flag_ = true;
    cv_.notify_all();
  }

  void reset() {
    std::lock_guard<std::mutex> lk(m_);
    flag_ = false;
  }
};

*1:ここで挙げたWindows APIイベントやPOSIXセマフォで実装した場合は、「1度しか使えない」という制約は存在しない。