muduo中的reactor模式实现浅析
最近刚刚看完了陈硕大神的《Linux多线程服务端编程》,对muduo源码也略有阅读,故作此总结。
Reactor模型
先来看看定义:
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.
简单地说,reactor就是由一个中控器去多路复用监听多个事件,事件触发后,中控器将事件转发给某个线程处理。reactor的演化可以大致分为三步:
- 方案1:单 Reactor 单线程;
![](https://pic2.zhimg.com/v2-614eb69d0186c32de123115b10c3c682_r.jpg?source=1940ef5c)
-
方案2:单 Reactor 多线程;
-
方案3:多 Reactor 多线程;
![](https://pic1.zhimg.com/v2-4da008d8b7f55a0c18bef0e87c5c5bb1_r.jpg?source=1940ef5c)
上述方案中,方案1不能利用多线程,性能受影响,不适用高性能服务器场合。
方案2采用线程池来处理业务,但是网络读写仍由主线程处理,性能上问题不大,因为单线程读写一般不比多线程读写慢,但是这引起资源竞争问题,因为线程池中的线程要去竞争对socket的读写,会带来同步开销。
所以muduo选用了方案3,也就是所谓的one loop per thread,有一个main Reactor负责accept连接,已建立的连接由sub Reactor全权控制:当一个新的连接到来,main Reactor只负责把它分配给某个sub Reactor,然后不再关心该连接,所有在该连接上的操作由sub Reactor完成这也带来了编码上的简化。
那么muduo是如何实现的多Reactor多线程呢?
muduo实现——单Reactor单线程
其实muduo本身支持上述的全部三种方案,而且只需要简单的几行代码就可以完成切换,具体用法可见 书中$6.6详解muduo多线程模型。
你应该了解这几个类(都在muduo/net包下):
- Channel,负责一个sockfd的IO事件分发,但不拥有sockfd。
- Poller,是对IO多路复用(即poll、epoll等)的封装。
- EventLoop,驱动类,和线程是1:1关系。
三者的关系是这样的(下面的代码中会忽略不重要的部分,例如定时器、时间戳、各种断言和错误处理等):
EventLoop类拥有一个Poller类成员,创建EventLoop对象时,其构造函数会初始化此数据成员poller_,
EventLoop::EventLoop() : /* ... */ poller_(Poller::newDefaultPoller(this)) {
/* ... */
}
由“第三方”来创建sockfd,依此sockfd创建一个新的Channel,并将此Channel绑定到一个EventLoop对象上,
int main() {
EventLoop loop;
int sockfd = /* ... */
Channel channel(&loop, sockfd);
/* ... */
}
此时的绑定是假绑定,loop对象并不知道此channel的存在,直到启用对channel的多路复用监听时:
int main() {
/* ... */
channel.setReadCallback(timeout); // 监听某种事件前必先注册相应的事件处理回调
channel.enableReading();
loop.loop(); // 开始eventloop,循环处理事件
/* ... */
}
Channel::enablexxx()会调用Channel::update(),进一步调用EventLoop::updateChannel(this),实现了将channel管理的sockfd注册进Poller:
void Channel::update()
{
addedToLoop_ = true;
loop_->updateChannel(this); // loop_数据成员是在构造当前Channel对象时绑定的EventLoop对象
}
/* ... */
void EventLoop::updateChannel(Channel* channel)
{
assert(channel->ownerLoop() == this