timerfd与定时器

该篇文章不是讨论如何使用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/epolltimerfd必须搭配使用,也就造成了添加一个定时器至少包含三个系统调用(timerfd_create(), timerfd_settime() , epoll_ctl()),这是我当初没有想到的,因为我的工作的项目中并没有大量的tcp连接与几乎没有几个定时器使用需求,所以并没有感觉会因为这三次系统调用而影响性能,若服务端要面对大量的网络连接与大量的定时器使用,那么这对性能应该存在一定影响.

那么,如何设计一个不使用timerfd去实现定时器使用在多线程事件响应程序就是这篇文章的主要内容,以下内容只是说一些大概想法,具体我目前也并未去实现,因为还有问题没思考清楚.

1、使用最小堆去管理定时器

最小堆的特性比较适合应用在这里.

2、使用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指针.

暂时这样.

你可能感兴趣的:(C++)