メインコンテンツへスキップ

1スレッドで複数タイマーを管理する

·1224 文字·
技術解説 C++
komori-n
著者
komori-n
目次

よく困るので汎用的に使えそうなタイマーライブラリを作った。

モチベ
#

一定時間経過後にコールバックで教えてほしいことがある。タイマーが1個であれば、以下のようにタイマー用のスレッドを立ててsleepさせれば実現できる。

// 5秒後にfnをコールしてもらう
void Timer::call_after_5s(std::function<void(void)> fn) {
    this->thread_ = std::thread([fn](void) {
        std::this_thread::sleep_for(5s);
        fn();
    });
}

ただ、タイマーの数が多くなると、スレッドの生成・破棄のコストが無視できない。そのため、スレッド数をケチって複数コールバックを実現するライブラリを作った。

ソースコードは以下のリポジトリで入手できる。

komori-n/multi-timer

C++
0
0

使い方
#

komori::MultiTimer<Task>のtemplate parameterには、lambda式やstd::functionkomori::unique_functionなどのoperator()で呼べるような型を代入する。中身の型は何でも良いが、用途を考えるとkomori::onetime_function<void(void)>を入れるのがおすすめである。

  komori::MultiTimer<komori::onetime_function<void(void)>> timer;
  timer.start_processing();

  timer.set([]{ std::cout << "3s" << std::endl; }, 3s);
  timer.set([]{ std::cout << "6s" << std::endl; }, 6s);

  timer.set([&]{
    std::cout << "2s" << std::endl;
    timer.set([&]{
      std::cout << "2s+2s" << std::endl;
      timer.set([&]{
        std::cout << "2s+2s+2s" << std::endl;
      }, 2s);
    }, 2s);
  }, 2s);

上記のプログラムを実行すると、出力結果は以下のようになる。

2s
3s
2s+2s
6s
2s+2s+2s

タイマーで指定した秒数が経過した時点で正しくコールバックが行われていることが確認できる。

実装解説
#

std::priority_queueで直近のコールバックまでsleepする。スケジュールの管理には以下の構造体を用いる。

using time_point = std::chrono::system_clock::time_point;

template <typename Task>
struct TaskSchedule {
  // add mutable to move with priority_queue.top()
  mutable Task task;
  time_point tp;
};

template <typename Task>
struct ScheduleCompare {
  bool operator()(const TaskSchedule<Task>& x, const TaskSchedule<Task>& y) const {
    return x.tp > y.tp;
  }
};

TaskSchedule同士はScheduleCompareにより比較される。priority_queueで最も締め切りが近い要素をtopへ来るようにScheduleCompare::operator()を定義している。

TaskSchedule::taskmutableが付与されているのは、queueの先頭からmoveで取り出すためである。

auto task = std::move(task_queue_.top().task);
task_queue_.pop();

std::priority_queue::top()の戻り値はconst参照である。もしこれが書き換え可能な参照を返却する場合、queueのtopが優先度最大でなくなる可能性があるためなので仕方がない。しかし、今回のケースではtaskはqueueの優先度には関係ないので、mutableを付与して中身を書き換えても問題にならない1


  1. 「top()の直後にpop()するならconst_castでconstを外せばいい」という主張もある。( https://stackoverflow.com/questions/20149471/move-out-element-of-std-priority-queue-in-c11)しかし、const_castでconst外しをするのはあまりに行儀が悪いと思う。" ↩︎

Related

安定な優先順位付きキュー(stable_priority_queue)を作る
·2340 文字
技術解説 C++ STL
move-onlyなlambda式のTaskQueueを作る
·1192 文字
技術解説 C++ STL
std::recursive_mutexを使う
·1067 文字
技術解説 C++ STL