C++线程安全的定时器(时间堆)实现
大约 3 分钟
time_heap(时间堆)
- 适合事件驱动系统的时间堆定时器,所有接口都尽量设计的简单易用。
- 现在大部分流行的定时器一般分为两种,时间轮,时间堆。基于设计上而言。时间轮可以设计为滴答检测超时事件,或者直接在循环中检查超时事件。而时间堆异同。
- 我之前就写过一个基于滴答检测超时事件的定时器,这样的机制会造成一个问题。那就是由于休眠唤醒的时间总会有微小的误差(每次滴答),那么每次的误差积累起来就会越来越大。导致定时时长过长的事件的时间误差越大。所以只能在某些不需要太过精准定时时长的地方使用。
- 接下来我们来看看在循环里检测超时事件的定时器吧,这种类型的定时器只有一个问题。那就是占用不必要的性能消耗。如果是在事件驱动系统那么将造成很大的性能浪费。
- 那我们的时间堆呢。它有问题吗?当然有。那就是如果连续插入大量定时时长过短的定时事件那么就会造成事件触发事件延迟,延迟的时间根据事件的数量和触发时间而定。
- 好处:
- 不浪费性能,使用WaitForSingleObject函数直接休眠到下一个事件触发时间再唤醒执行任务;
- 适合核心数较少的 cpu, 不会出现一个循环占满 cpu 的情况;
性能测试(Release_x64)
- 10w_50ms_timer, 从添加定时任务到最后一个定时任务结束消耗 230ms 左右, 循环检测类定时器消耗 30ms 左右
- 100w_50ms_timer, 从添加定时任务到最后一个定时任务结束消耗 2300ms 左右, 循环检测类定时器消耗 350ms 左右
- 10w_2000ms_timer, 从添加定时任务到最后一个定时任务结束消耗 150ms 左右, 循环检测类定时器消耗 30ms 左右
- 100w_2000ms_timer, 从添加定时任务到最后一个定时任务结束消耗 1200ms 左右, 循环检测类定时器消耗 280ms 左右
上面已经说了优缺点,接下来说下可以优化的点:
- 在时间堆中使用内存池。
- 加入异步执行任务。
备注:如果不在意性能消耗可以使用循环类检测超时任务的定时器,更加精准.
使用代码:
auto& timer = time_heap::instance();
double second = 5; //定时时长(单位:秒,由于是double可以传入0.05这样的数字,会自动转为毫秒)
uint32_t count = 5; //执行次数
//timer.add函数的返回值是一个std::shared_ptr对象。不用手动销毁
auto timer1 = timer.add(second, count, [&](int index) {
printf("%d\n");
return index;
}
, 1); //不定参,可以使用任意类型个数的参数
//获取事件函数返回值(若未事件未执行完成将堵塞)
auto result = timer1->get();
//安全的获取事件函数返回值(若未事件未执行完成将堵塞,超过count次get完成后再次get将cash)
if (timer1->vaild) {
auto result = timer1->get();
}
timer1->del(); //删除定时事件
代码库在 github--> 点击此处进入