该篇文章不是讨论如何使用timerfd去实现定时器,而是说明在某一情况下,不建议使用 timerfd 去实现,而是采取其他方法去实现
在逛知乎看到这么一个问题,“muduo库在实际项目中使用的人多吗?”,在评论区下面看到了这么一条评论:
一个网络编程库,timer是重中之重,比到底是用epoll还是select都重要。当然,话说回来,再吊的库无非也就是个heap为本的数据结构在支持,无非是有些库喜欢说自己的heap实现比别人都高效,比如haproxy。但muduo却独辟蹊径,用timerfd,泥玛又是一个高级特性啊,很唬人的。由kernel帮你管理timer,是不是很吊。
之前在工作中实现的一个多线程服务端程序参考了muduo库的设计或者说是one loop per thread模型,在实现的过程中,因为当初编程经验不太足,所以实现思路大多都是按照muduo的思路去走,在定时器的实现上使用了timerfd
(linux下的特性),而与muduo不同的是使用了最小堆去管理定时器.
使用timerfd
的优点大概是:通过使用timerfd
的相关接口,打开一个文件描述符,这样我们就可以通过select/poll/epoll
去监测是否有定时器触发.
缺点:select/poll/epoll
和timerfd
必须搭配使用,也就造成了添加一个定时器至少包含三个系统调用(timerfd_create(), timerfd_settime() , epoll_ctl()
),这是我当初没有想到的,因为我的工作的项目中并没有大量的tcp连接与几乎没有几个定时器使用需求,所以并没有感觉会因为这三次系统调用而影响性能,若服务端要面对大量的网络连接与大量的定时器使用,那么这对性能应该存在一定影响.
那么,如何设计一个不使用timerfd
去实现定时器使用在多线程事件响应程序就是这篇文章的主要内容,以下内容只是说一些大概想法,具体我目前也并未去实现,因为还有问题没思考清楚.
最小堆的特性比较适合应用在这里.
pthread/std::thread
和条件变量的wait_for/wait_until
去实现判断定时器是否触发,而不是使用signal
去处理.因为放弃了使用timerfd
,所以我只能想到使用开辟一个线程去处理定时器,且只用一个线程去判断一/多个线程注册的定时器是否触发,保证不滥用线程,但是这也引来了一个问题,该让哪个线程来调用触发的定时器的回调函数,在定时器处理线程中调用会影响定时的精度,最好就是哪个线程注册的定时器就由那个线程去处理回调函数,但是这就会不难避免在定时器的结构中记录线程的信息,而在事件响应的模式中,就需要在定时器中加入event_loop
,就比如以下代码中:
在下面代码中会备注一些功能作用以及相关介绍.
//事件循环event_loop
typedef std::function timer_callback;
class event_loop{
public:
...
...
void add_every(timer_callback &cb,uint32_t sec,...);//添加定时器
void wakeup();//用于唤醒loop(),用pipe或者eventfd..
void loop();
void appendPendingTask();
void runPendingTask();
private:
std::vector _functors;
std::mutex mtx;
}
void event_loop::runPendingTask()
{
std::vector tmp;
{
unique_lock lck(mtx);
tmp.swap(_functors);
}
for(auto& i:tmp)
i();
}
void event_loop::loop()
{
poll()
//处理select/poll/epoll
for(...){
...
}
runPendingTask();
}
//timer
struct event{
timer_callback cb;
event_loop *loop; // loop->appendPendingTask(cb)
uint64_t timestamp;//or std::chrono::...::time_point
...
};
//用于判断定时器到时
//应该写为单例模式,且应该在最后被销毁或者不销毁,直接关闭进程
class timer_loop{
public:
...
void loop();
...
private:
void heap_add(...);
void heap_del(..);
void heap_update(...);
void heap_pop();
...
std::vector _event_lst;
int _size;
};
void timer_loop()
{
event top;
while(_quit){
top=timer_heap.top();//
if(...){
....
conv.wait_until(...);
if(timeout){
....
....
//相当于把触发的定时器回调函数和event_loop整理好
//然后把需要调用的callback封装成一个callback传入event_loop中
std::map m;
std::vector _
while (_size != 0 && top >= _event_lst[0].tp) {
event te = _event_lst[0];
m[te.loop].push_back(te.cb);
if (te.interval) {
te.tp = clock_type::now() +std::chrono::seconds(te.sec)++std::chrono::milliseconds(te.ms);
heap_update(0);
continue;
}
heap_pop();
}
for(auto &i:m){
auto ptask=[](std::vector vec){
auto tvec=std::move(vec);
for(auto &cb:tvec){
cb();
}
}
i.loop->appendPendingTask(std::bind(ptask,i.second));
}
}
}
else{
continue;
}
}
}
以上是我的假设代码流程,当在多线程多个event_loop
进行析构的时候且确保timer_loop
是存活的情况下,将注册的定时器给删掉,确保不会访问到空的event_loop
指针.
暂时这样.