I/O 多路复用模式:Reactor & Proactor
一般地,I/O 多路复用机制(I/O multiplexing mechanisms)都依赖于一个事件多路分离器(Event Demultiplexer)。
我们常见的事件多路分用器包括:Linux 的 epoll 和 Windows 的 IOCP。
事件多路分离器(Event Demultiplexer)可将来自事件源的 I/O 事件分离出来,并分发到对应的 事件处理器 (Event Handler)进行 read/write。
两个与事件分离器有关的模式是 Reactor 和 Proactor,Reactor 模式采用同步 IO,而 Proactor 采用异步 IO。
Reactor 模式
在 Reactor 中,事件多路分离器 等待文件描述符状态变为读写操作准备就绪状态,然后将就绪事件传递给对应的 处理器,最后由 处理器 负责完成实际的读写工作。
Linux epoll 使用 Reactor 模式,Reactor 模式使用同步 I/O(一般来说)。Reactor 的标准(典型)的工作方式是:
- Reactor 线程中, epoll 注册读/写等等事件
- epoll 等待事件到来
- 事件到来,Reactor 把事件分发给处理器(往往使用线程池跑处理器)
- 处理器线程: 读写数据(调用 read/write, 从内核 buff 将数据拷贝到用户态 buff)
- 处理器线程进行处理(decode 数据, 执行业务代码, encode 数据)
Netty 的 Reactor 线程模型 @link:: [[../12.Java/Java-Tutorials.09.NIO&Netty#Reactor三种常见线程模型]]
Proactor 模式
而在 Proactor 模式中,处理器 只负责发起异步读写操作。 处理器 传递给操作系统 用户态的数据缓冲区,之后的 IO 操作、以及内核态缓冲区→ 用户态缓冲区操作,这些都由操作系统来完成。
比如,在 windows 上,处理器发起一个异步 IO 操作,再由事件分离器等待 IOCompletion 事件。IOCompletion 通知的时候, 数据已经被拷贝到处理器的 buff 了.
典型的异步模式实现,都建立在操作系统支持异步 API 的基础之上,我们将这种实现称为“系统级”异步或“真”异步,因为应用程序完全依赖操作系统执行真正的 IO 工作。
Windows IOCP 使用 Proactor 模式,Proactor 模式使用异步 I/O。Proactor 的标准(典型)的工作方式是:
- 处理器发起异步读操作(注意:操作系统必须支持异步 IO)。在这种情况下,处理器无视 IO 就绪事件,它关注的是完成事件。
- 事件分离器等待操作完成事件
- 在分离器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分离器读操作完成。
- 事件分离器呼唤处理器。
- 事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分离器。
以上参考: Reactor VS Proactor 模式 @ref
两种模式的比较
比较实现
- 在 Reactor 模式,用户代码的责任是: 在收到 IO事件后进行实际的 IO 操作;
- 在 Proactor 模式,用户代码需要负责收到 IO事件后的所有操作;
优势和劣势
✔︎ Reactor 优势
- Reactor 实现相对简单,对于耗时短的处理场景处理高效;
- 操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性;
- 事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁;
- 事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来,
× Reactor 劣势
- Reactor 处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;
✔︎ Proactor 优势
- Proactor 性能更高,能够处理耗时长的并发场景;
× Proactor 劣势
- Proactor 依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,比较优秀的如 windows IOCP(完成端口),但由于其 windows 系统用于服务器的局限性,目前应用范围较小;
而 Unix/Linux 系统对纯异步的支持尚不成熟,应用事件驱动的主流还是通过 select/epoll 来实现;
适用场景
- Reactor:同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序;
- Proactor:异步接收和同时处理多个服务请求的事件驱动程序;
在实际工程中的使用
- Reactor: libevent / libev /libuv / ZeroMQ / Event Library in Redis
- Proactor: Windows IOCP / Boost.Asio