为什么设计和实现TimerController?
最新的TimerController代码保存在Github上面:https://github.com/zuocheng-liu/StemCell ,包含timer_controller.h 和 timer_controller.cpp两个文件,欢迎审阅!
因为软件设计中面临了一些实际问题
尤其在使用C++开发网络应用时常遇到下面的问题:
一、软件设计中,不会缺少通过使用定时器的来实现的场景,比如超时控制、定时任务、周期任务。
二、C/C++标准库中只有最原始的定时接口。没有提供功能完备的库来满足上面提到复杂场景。
三、第三方库中的定时器,往往存在一些问题,比如:
- libevent、libev、libue 不是线程安全的,在多线程系统中,为了保证线程安全需要额外再进行封装。
- redis的异步库libae对延时时间的处理是不准确的。
以上问题会让开发者开发新系统时带来一些困扰,但C++11新特性的出现,带来了解决上面问题的新思路。
C++11的新特性让定时器的实现更简单友好
TimerController接口更友好
接口参数支持C++11的lamaba表达式,让定时器的接口对开发人员更加友好。
代码更精简
TimerController的代码总计300~400行,而且功能完备,代码健壮。
C++11的新特性的使用,让代码更简洁,增强代码的可读性、可维护性。
保证线程安全
线程安全,是绕不开的问题。第三方库libevent等,在多线程环境中使用总是危险的。TimerController在设计之初就保证多线程环境下运行的安全性。
没有第三方依赖。
TimerController,没有依赖任何第三方库,完全依靠C/C++标准库和C++11的新特性来实现。
TimerController 接口设计
class TimerController {
bool init(); // 初始化资源,并启动定时器
void stop(); // 停止定时器,所有定时任务失效
// 定时运行任务,参数delay_time单位是毫秒。后面参数是lamba表达式
template<class F, class... Args>
void delayProcess(uint32_t delay_time, F&& f, Args&&... args);
// 周期运行任务,参数interval单位是毫秒,后面参数是lamba表达式。
template<class F, class... Args>
void cycleProcess(uint32_t interval, F&& f, Args&&... args);
}
用一个实例来讲解TimerController的使用方法:
#include <iostream>
#include "timer_controller.h"
using namespace std;
using namespace StemCell;
int main() {
srand((unsigned)time(NULL));
try {
TimerController tc;
tc.init(); // 初始化 TimerController
tc.cycleProcess(500, [=]() { cout << "cycle 0.5 sec" << endl; });
for (int i = 0; i < 80; ++i) {
// 随机产生80个延时任务,并延时执行
auto seed = rand() % 8000;
tc.delayProcess(seed + 1, [=]() { cout << "delay:" << seed << endl; });
}
sleep(8); // 主线程睡眠8秒,让延时任务得以执行
tc.stop(); // 停止 TimerController
cout << "tc stoped!" << endl;
} catch (exception& e) {
cout << "error:" << e.what();
}
return 0;
}
TimerController 实现原理
TimerController如何实现定时的原理和libevent并无不同,都使用了Reactor的设计模式。但是TimerController通过生产者、消费者模型,来实现多线程添加定时任务,并保证线程安全。TimerController使用了C++11的新特性,简化了代码实现。
- 使用最小堆来管理延时任务和周期任务
- 使用1个timerfd配合epoll来实现定时任务的激活
- 使用1个eventfd配合epoll来实现定时任务的添加
- 使用一个线程安全的队列,实现生产者消费者模型。TimerController使用场景为多线程写,TimerController内部1个线程来读。
TimerController 高级用法
高延时的任务的处理
TimerController内部只有1个线程在执行定时任务。当高延时的任务增多时,可能会影响到任务运行的调度时间,高延时的任务需要在放入新的线程中运行。示例如下:
TimerController tc;
tc.init(); // 初始化 TimerController
// 把任务放入新线程或线程池中
tc.delayProcess(50, []() { std::thread([]() { do_long_time_task();}) });
TimerController保持全局单例
为了系统简洁,TimerController全局单例即可。
auto& tc = Singleton< TimerController >::GetInstance();
其他
如何避免CPU负载高时,定时器失准的问题?
TimerController 有待改进的点
- 无锁化,目前使用了自旋锁在保证task队列的线程间互斥,后续可使用无锁队列替代有锁队列。
- TimerController精度目前只有1毫秒,主要因为博主做网络开发都是毫秒级的,后续可以让TimerController支持更小的精度。
- TimerController 使用了epoll、timerfd、eventfd等,只能在linux平台上面使用
源码地址
具体实现在 timer_controller.h 和 timer_controller.cpp两个文件里面。