Eventloop事件循环:反应器(reactor):负责IO和定时器事件的分派。
Reator总结性:(Eventloop,Poller,Channel,Timer)
一个事件循环对应一个IO线程,IO线程执行EventLoop事件主循环,该主循环loop调用IO复用器poller监听事件集合,并将就绪事件通过事件分发器Channel执行相应的事件回调。
1. Class Channel:事件分发器,主要用于事件注册和事件处理。记录了描述符
fd的注册事件和就绪事件以及就绪事件的回调函数;只属于一个Eventloop,每个channel对象自始至终只负责一个文件描述符fd的IO事件的分发,但它并不拥有这个fd,把不同的IO事件分发为不同的回调,如:ReadCallback和WriteCallback。
关键数据:intfd_负责的描述符;intevent_文件描述符的注册事件;
int revents_文件描述符的就绪事件,以及事件回调readCallback等。
主要函数调用过程:当一个fd想要注册事件并在事件就绪时执行相应的就
事件回调时:
Channel::update(this)->
EventLoop::updateChannel(Channel*)->
Poller::updateChannel(Channel*)调用链向poll系统调用的侦听事件表注册或者修改注册事件。
Poller修改Channel,若Channel已经存在于Poller的vector
Channel作为事件分发器其核心结构是Channel::handleEvent()该函数执行fd上就绪事件相应的回调。
void set_revents(int revt)->handleEvent():
//设定fd的就绪事件类型,在poll返回就绪事件后将就绪事件类型传给set_revents函数,然后此函数传给handlevent,handleEvent根据就绪事件的类型决定执行那个事件回调函数。
Channel:~Channel()->
EventLoop::removeChannel(Channel*)->
Poller::removeChannel(Channel*)
将Poller中的Channel*移除防止空悬指针。因为Channel的生命周期和Poller/EventLoop不一样长。
2. class poller:实现IO multiplexing,其功能仅仅是poll系统调用的简单封装,它并不拥有channel,只是对channel*指针进行组织和监测(所以channel必须要自己解除注册,以防止有野指针)。
关键数据结构:
vector
map
*vector中存储的是channel->fd,而map中存储的是fd和channel。
关键函数调用:
Poller::poll(inttimeoutMs,vector
调用poll监听事件集合,并在timeout时间内就绪的事件集合通过activeChannels返回(EventLoop中loop()获得该集合后,调用Channels相应的回调函数)。这里Poller::poll()返回后本可以执行Channel::handleEvent()将就绪事件的回调执行了,但并没有这样做的原因:channel::handleEvent()可能修改Poller的两个容器,即添加或者删除,在遍历容器时会很危险的,同时为了简化Poller,Poller的职责就是IO复用,至于事件分发还是交给Channel自己完成。
3. 定时器:传统的定时通过select/poll实现,现在通过timerfd实现定时,采用文件描述符实现定时有利于统一事件源,这些将为EventLoop实现定时功能。
实现:主要有几个类-----
Timer定时器包含超时回调;
TimerId定时器加上一个唯一的ID;
Timestamp时间戳;
TimerQueue管理所有的定时器;(TimerQueue和TimerId是友元关系)
Timer:定时器,具有一个超时时间和超时回调,超时时间有当前时间戳加上一个超时时间生成一个绝对时间,定时器回调函数Timer::run(callback)-TimerCallback。
TimerQueue:定时器队列,用于管理所有的定时器,当定时器超时后执行相应的Timer::run()定时器回调。
数据结构:采用set
原因在一个时间点可能有多个时间戳TimeStamp超时,而查找只返回一个。通过给timerfd一个超时时间实现超时计时,通过Channel管理timerfd,然后向EventLoop和Poller注册timerfd的可读事件,当timerfd的可读事件就绪表明某一个超时时间到点了,TimerQueue::handleRead()遍历set容器找出那些超时的定时器并执行Time::run()实现超时回调。
那么timerfd如何实现多个定时器超时计时的呢?
每次向set插入一个定时器Timer的时候就比较set的头元素的超时时间,若新插入的超时时间小,则更新timerfd的时间,从而保证timerfd始终是set中最近的一个超时时间,当timerfd可读时,需要遍历容器set,因为可能此时又多个Timer超时,为了复用定时器,每次执行完定时器回调后都要检查定时器是否需要再次定时。这里的关键是采用timerfd实现统一事件源。
4.EventLoop:事件循环
最重要的调用过程:EventLoop::loop()->Poler::poll()通过此调用获得一个vector
//实现事件循环(poller)
//实现定时回调功能(timer),通过timerfd和TimerQueue实现
//实现用户任务回调(),为了线程安全可能有其他线程向IO线程的EventLoop添加任务,此时通过eventfd通知eventloop执行用户任务
什么是线程转移?线程转移实现就可以通过两个函数实现,如:假设类one隶属于线程B,线程A调用one的方法fun,fun向one注册一个回调,从而将具体操作转移到one的所属线程B中去执行。)
Class EventLoop实现用户定时回调:当有了定时器TimerQueue后,EventLoop可以实现几个定时器接口:
EventLoop::runAt(TimeStamp ,TimerCallback)在一个绝对时间执行一个回调TimerCallback;
EventLoop::runAfter(double delay,TimerCallback)实现一个相对时间回调,这里将使用到TimerQueue执行完超时回调后检查定时器是否需要再次定时。
Class EventLoop实现用户指定任务回调:EventLoop::runInLoop(boost::function
Q:保证one loop per thread,那么如何判断该eventloop属于该线程
A:自己理解:有一个线程id,判断当前线程的id和该EventLoop所属线程是否在一个线程。
voidEventLoop::abortNotInLoopThread()
{
LOG_FATAL <<"EventLoop::abortNotInLoopThread - EventLoop " << this
<< " was created inthreadId_ = " << threadId_
<< ", current thread id= " << CurrentThread::tid();
}
重点要理解的:
1. oneloop per thread实现过程中,eventloop如何被唤醒?
2. Eventpool,Channel,poller的作用,以及相互调用关系。
3. 统一事件源,timer以及timerqueue做了什么。
Eventpool中重要的函数实现: