两种I/O多路复用模式:Reactor和Proactor
I/O多路复用机制都依赖于一个事件多路分离器(Event Demultiplexer)。分离器对象可将来自事件源的I/O事件分离出来,并分发到对应的read/write事件处理器(Event Handler)。开发人员预先注册需要处理的事件及其事件处理器(或回调函数);事件分离器负责将请求事件传递给事件处理器。两个与事件分离器有关的模式是Reactor和Proactor。Reactor模式采用同步IO,而Proactor采用异步IO。
Reactor模式中,事件分离器负责等待文件描述符或socket为读写操作准备就绪,然后将就绪事件传递给对应的处理器,最后由处理器负责完成实际的读写工作。
Proactor模式中,处理器--或者兼任处理器的事件分离器,只负责发起异步读写操作。IO操作本身由操作系统来完成。传递给操作系统的参数需要包括用户定义的数据缓冲区地址和数据大小,操作系统才能从中得到写出操作所需数据,或写入从socket读到的数据。事件分离器捕获IO操作完成事件,然后将事件传递给对应处理器。比如,处理器发起一个异步IO操作,再由事件分离器等待IO完成事件。典型的异步模式实现,都建立在操作系统支持异步API的基础之上,我们将这种实现称为“系统级”异步或“真”异步,因为应用程序完全依赖操作系统执行真正的IO工作。
总的来说,基于Reactor模式的I/O事件等待以及事件I/O读写都是由用户程序处理。基于Proactor模式的I/O事件等待由用户程序管理而事件I/O读写由操作系统来处理。
Linux下Reactor模式:
- 注册相应的事件处理器到事件分离器中
- 事件分离器等待事件
- 事件到来,激活分离器,分离器调用事件对应的处理器
- 事件处理器完成实际的I/O操作,根据I/O结果注册新的事件(例如一个套接字连接完成后注册读事件,读完成后注册写事件),然后返还控制权
Linux下Proactor模式:
- 处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这种情况下,处理器无视IO就绪事件,它关注的是完成事件
- 事件分离器等待操作完成事件
- 在分离器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分离器读操作完成
- 事件分离器呼唤处理器
- 事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步写操作,并将控制权返回事件分离器
- 事件分离器呼唤处理器通知写操作已经完成,后续处理由处理器负责执行
实现
Reactor 实现了一个被动的事件分离和分发模型,服务等待请求事件的到来,再通过不受间断的同步处理事件,从而做出反应;
Proactor实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响)
优点
Reactor实现相对简单,对于耗时短的处理场景处理高效;
操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性;
事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来,
Proactor性能更高,能够处理耗时长的并发场景;
缺点
Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;
Proactor实现逻辑复杂;依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,实现优秀的如windows IOCP,但由于其windows系统用于服务器的局限性,目前应用范围较小;而Linux系统对纯异步的支持有限,应用事件驱动的主流还是通过select/epoll来实现;
适用场景
Reactor : 同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序; Proactor:异步接收和同时处理多个服务请求的事件驱动程序;