muduo的设计采用高并发服务器框架中的one loop per thread模式,即一个线程一个事件循环。
这里的loop,其实就是muduo中的EventLoop
,所以到目前为止,不管是Poller
,Channel
还是TimerQueue
都仅仅是单线程下的任务,因为这些都依赖于EventLoop
。这每一个EventLoop
,其实也就是一个Reactor模型。
而多线程体现在EventLoop
的上层,即在EventLoop
上层有一个线程池,线程池中每一个线程运行一个EventLoop
,也就是Reactor + 线程池的设计模式
梳理一下
- 每个muduo网络库有一个事件驱动循环线程池EventLoopThreadPool
- 每个线程池中有多个事件驱动线程EventLoopThread
- 每个线程运行一个EventLoop事件循环
- 每个EventLoop事件循环包含一个io复用Poller,一个计时器队列TimerQueue
- 每个Poller监听多个Channel,TimerQueue其实也是一个Channel
- 每个Channel对应一个fd,在Channel被激活后调用回调函数
- 每个回调函数是在EventLoop所在线程执行
- 所有激活的Channel回调结束后EventLoop继续让Poller监听
所以调用回调函数的过程中是同步的,如果回调函数执行时间很长,那么这个EventLoop
所在线程就会等待很久之后才会再次调用poll。
整个muduo网络库实际上是由Reactor + 线程池实现的,线程池中每一个线程都是一个Reactor模型。在处理大并发的服务器任务上有很大优势。
简化的关系图如下,EventLoop只涉及Poller,Channel(简单涉及TcpConnection)和TimerQueue。
- 白色三角,继承
- 黑色菱形,聚合
一个事件驱动循环EventLoop其实就是一个Reactor模型,是一个单线程任务。主要包含io复用函数Poller,定时器队列TimerQueue以及激活队列。其他的就是一些辅助变量
typedef std::vector<Channel*> ChannelList;
bool looping_; /* atomic */
std::atomic<bool> quit_;
bool eventHandling_; /* atomic */
bool callingPendingFunctors_; /* atomic */
int64_t iteration_;
/* 创建时保存当前事件循环所在线程,用于之后运行时判断使用EventLoop的线程是否是EventLoop所属的线程 */
const pid_t threadId_;
/* poll返回的时间,用于计算从激活到调用回调函数的延迟 */
Timestamp pollReturnTime_;
/* io多路复用 */
std::unique_ptr<Poller> poller_;
/* 定时器队列 */
std::unique_ptr<TimerQueue> timerQueue_;
/* 唤醒当前线程的描述符 */
int wakeupFd_;
// unlike in TimerQueue, which is an internal class,
// we don't expose Channel to client.
/*
* 用于唤醒当前线程,因为当前线程主要阻塞在poll函数上
* 所以唤醒的方法就是手动激活这个wakeupChannel_,即写入几个字节让Channel变为可读
* 注: 这个Channel也注册到Poller中
*/
std::unique_ptr<Channel> wakeupChannel_;
boost::any context_;
// scratch variables
/*
* 激活队列,poll函数在返回前将所有激活的Channel添加到激活队列中
* 在当前事件循环中的所有Channel在Poller中
*/
ChannelList activeChannels_;
/* 当前执行回调函数的Channel */
Channel* currentActiveChannel_;
/*
* queueInLoop添加函数时给pendingFunctors_上锁,防止多个线程同时添加
*
* mutable,突破const限制,在被const声明的函数仍然可以更改这个变量
*/
mutable MutexLock mutex_;