muduo源码分析之EventLoop

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<pollfd>,则Poller调用Channel::events()获得事件并重置vector中的pollfd,若Channel没有在vector中则向Poller的vector添加新的文件描述符事件到事件表中,并将vector.size()作为Channel::ser_index()作为channel记住自己在Poller中的位置。

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<struct pollfd>pollfds_========事件结构体数组;

map<int,Channel*>channels_========用于文件描述符fd到Channel的映射便于快速查找到相应的Channel,如poll返回后遍历pollfds_找到就绪事件的fd再通过channels_找到fd的channel然后就可以通过Channel::handleEvent执行就绪事件回调。

*vector中存储的是channel->fd,而map中存储的是fd和channel。

    关键函数调用:

Poller::poll(inttimeoutMs,vector<Channel*>activeChannels)(其中调用了PollPoller::fillActiveChannels):

调用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<pair<TimeStamp,Timer*>>,用pair<TimeStamp,Timer*>

原因在一个时间点可能有多个时间戳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<Channel*>activeChannels_的就绪事件集合,再遍历该容器,执行每个Channel的Channel::handleEvent()完成相应的就绪事件回调,此时一个Reactor模式完成。

//实现事件循环(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<void()>)将任务添加到队列中(线程转移)。EventLoop如何获得有任务到来?-----通过eventfd实现线程通信,具体做法是:其他线程向EventLoop::vector<>添加任务T,然后通过EventLoop::wakeup()向eventfd写一个int,eventfd的回调函数handleRead()读取int,从而相当于EventLoop被唤醒,此时loop中遍历队列执行任务,采用Channel管理eventfd,Poller侦听eventfd体现了eventfd可以统一事件源的优势。

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中重要的函数实现:





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值