よく困るので汎用的に使えそうなタイマーライブラリを作った。
モチベ#
一定時間経過後にコールバックで教えてほしいことがある。タイマーが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::MultiTimer<Task>
のtemplate parameterには、lambda式やstd::function
、komori::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::task
にmutable
が付与されているのは、queueの先頭からmoveで取り出すためである。
auto task = std::move(task_queue_.top().task);
task_queue_.pop();
std::priority_queue::top()
の戻り値はconst参照である。もしこれが書き換え可能な参照を返却する場合、queueのtopが優先度最大でなくなる可能性があるためなので仕方がない。しかし、今回のケースではtaskはqueueの優先度には関係ないので、mutableを付与して中身を書き換えても問題にならない1。
「top()の直後にpop()するならconst_castでconstを外せばいい」という主張もある。(https://stackoverflow.com/questions/20149471/move-out-element-of-std-priority-queue-in-c11)しかし、const_castでconst外しをするのはあまりに行儀が悪いと思う。" ↩︎