高级IO】④Reactor事件驱动模型探究
Reactor模型是相对与传统的阻塞式I/O模型提出来的,节约线程资源和提升业务处理并发量的IO模型,它主要基于I/O多路复用
配合线程池
的思想来实现。不过实际上Reactor模式依然是一种同步I/O模型
对比参照-传统阻塞式I/O模型
可以看到当并发规模增大时,这一模型有着显著的性能问题:
- 线程资源消耗大: 每一个客户端链接都需要唯一的线程维护链接和处理业务,开销迅速上升
- 线程利用率低: 当出现连接数很高而实时活跃连接数较低时,大部分线程处于阻塞状态,浪费了大部分线程的处理性能。
Reactor模型的提出
基于传统阻塞式I/O的两大痛点,Reactor模型使用了两大思想对应解决:
I/O多路复用
: 多个连接由同一个对象同时监听,当事件就绪时输出所有就绪的链接(在C++中则是文件描述符)。这样维护链接的任务就可以统一交给实现I/O多路复用的对象中,而不用分散给每一个线程。线程池
: 当有任务就绪时,则将其分配给线程池中的线程,而但任务完成后,就将空闲的线程还给线程池,这样可以解决线程利用率低的问题。当然相应地它也引出了动态扩容和负载均衡等进一步优化性能的问题。
Reactor 模式的组成元素
- Reactor 反应器
- 持有Selector对象
- 调用Selector接口,负责监听&分发事件(如连接、读写)
- Selector 多路复用器
- 调用系统接口实现多路复用
- Handler 事件处理器
- 负责解码和业务处理
常见线程模型
根据Reactor数量和线程的数量可以分为三种常见模型
- 单Reactor单线程模型
- 所有操作在同一个线程完成
- 单Reactor多线程模型
- Reactor主线程处理I/O,业务逻辑丢给线程池
- Reactor依旧是性能瓶颈 ,且存在读事件与连接事件竞争的问题
- 主从Reactor多线程模型
- 主Reactor只处理连接accept,子Reactor负责I/O读写
Reactor模型实现
接下来我们使用C++来实现这三种常见的模型
单Reactor单线程模型
我们通过三个文件实现
- EventHandler.hpp 实现事件处理器
- Selector.hpp 实现I/O多路转接
- Reactor.hpp 实现Reactor模式
EventHandler.hpp
1 |
|
Selector.hpp
1 |
|
Reactor.hpp
1 |
|
单Reactor多线程模型
我们来在第一个模型的基础上修改出第二个模型
因为新增了多线程的特性,我们有如下工作要做:
Selector
加锁保护各个接口EventHandler
功能扩充- 读写处理分离
- 新增写操作的缓冲区
- 新增针对每个
fd
的锁 - 新增针对锁图的互斥锁
Reactor
微调: 将监听套接字改为非阻塞等ThreadPool
新增- 维护任务队列
- 分配线程处理任务
Selector.hpp
1 |
|
EventHalder.hpp
1 |
|
Reactor.hpp
1 |
|
ThreadPool.hpp
1 |
|
主从Reactor多线程模型
主线程职责
- 唯一负责 accept() 新连接通过 epoll 监听 _listen_sock 的 EPOLLIN 事件收到新连接后通过Round-Robin或负载均衡分发给子线程
子线程职责
- 每个子线程有自己的 epoll 实例只处理已建立连接的读写事件不参与监听套接字的管理
重要改动
Selector
对象传递改成使用接口参数传递指针,使之更加灵活Acceptor
从Disatcher
中分离出来,专门由主线程调用- 新增
subReactorLoop
子线程入口函数,专门处理客户端的IO请求
Selector.hpp
1 |
|
EventHalder.hpp
1 |
|
ThreadPool.hpp
1 |
|
ReactorServer.hpp
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 supdriver的博客!
评论