muduo库学习(1):Reactor模型的设计思想
2025/11/5:
几乎有一年没发学习的内容了,这里对这一个多月学习muduo的历程做一个阶段性的总结。在这段时间的学习中,同时发现了自己在一些基础的不足之处。
文章目录
- Reactor漫谈
- IO多路复用
- what made a Reactor?
Reactor漫谈
作为认识muduo模块的开始,我们首先需要认识Reactor这一公认优秀的网络架构。
IO多路复用
在处理IO的时候,阻塞和非阻塞都是同步IO,只有使用特殊api的才是异步IO。
——陈硕
这里简单讲一下网络IO的一些分类:
网络IO阶段一(操作系统)
- 数据准备(tcp缓冲区)
- 阻塞:让调用IO方法的进程进入阻塞状态(recv)
- 非阻塞:不会改变线程的状态,通过返回值判断数据的读写情况
网络IO阶段二(应用程序)
- IO的同步和异步
- 同步:recv往内核中的tcpbuffer向程序中的buffer搬运数据,
不由操作系统完成- 同步IO接口:select poll epoll recv等基本都是
- 异步:应用程序做自己的事,当调用异步IO接口时,将
sockfd、buffer、通知信号都交给操作系统,操作系统完成数据的搬运,完成后通过信号通知程序。相当于提供一个回调函数,类似于dma通知cpu完成内存的映射- 异步IO接口:aioread aiowrite
- 缺点:编程复杂
- 同步:recv往内核中的tcpbuffer向程序中的buffer搬运数据,
是否异步的关键在于是否存在通知机制,以及任务是否由他人处理。最简单粗暴,也是最有效的判断在于我们上面所引用的话:在处理IO的时候,阻塞和非阻塞都是同步IO,只有使用特殊api的才是异步IO。因此,我们可以认为异步IO是极为特殊的一种IO操作,下面暂时不做讨论。
由上面的话我们也可以区分业务层面的同步和异步操作:
- 同步操作
- A等待B做完事并提供返回值再继续处理
- 异步操作
- A告诉B感兴趣的事件和处理方式,A继续执行自己的操作逻辑,等B监听到对应事件发生时通知A,A再开始相应的处理逻辑(例:node.js 异步框架)
一个经典的IO操作包含两个过程:数据就绪和数据读写
数据准备:根据系统IO操作的就绪状态,分为阻塞、非阻塞两种,表现形式为线程阻塞或者直接返回。
例:管道读写默认阻塞
数据读写:根据应用程序和内核的交互方式,分为同步、异步两种。
根据上面的信息,我们给出Reactor的分类,Reactor是一个同步非阻塞的IO多路复用模式,不会阻塞在读写(send/recv/read/write)上,而是在一个线程下管理多个用于读写的fd(C++一般使用epoll),根据读写事件的通知来高效进行读写任务。
与之相对的,一个同步阻塞的IO模型,往往使用一个线程来阻塞在一个fd的读写上;一个同步非阻塞的模型也会使用一个线程来进行一个读写任务的轮询。因此,Reactor一次管理多个读写任务的模式相比同步阻塞与非阻塞的模式明显要高效很多。
或者可以这样说:IO多路复用用于阻塞地获取可读取的fd,在每个fd上非阻塞地读入数据以防止在fd上阻塞难以回到epoll wait。
what made a Reactor?
良好的网络服务器设计需要将多线程服务端编程的问题转化为如何设计一个高效且容易使用的eventLoop。
——陈硕

Reactor的核心思想就是设计一个高效且容易使用的eventLoop,具体如下所示:
在图中,Reactor组件下的Event集合循环即为eventLoop,该组件从Demultiplex下的epollwait循环中获取发生的事件,并调用对应的EventHandler进行事件处理。在muduo的结构中,一个线程中运行一个eventLoop,即 one thread per loop。
一开始可能对Reactor和Demultiplex两个组件的分离有所疑惑,其实这两个组件的分离关系到在epollwait进行阻塞等待时,能通过唤醒等方法在其中加入新fd的操作。
