I/O多路复用模式: Reactor & Proactor
一般地,I/O多路复用机制(I/O multiplexing mechanisms)都依赖于一个事件多路分离器(Event Demultiplexer)。我们常见的事件多路分用器包括:Linux 的 EPOLL 和 Windows 的 IOCP。
分离器对象可将来自事件源的I/O事件分离出来,并分发到对应的read/write事件处理器(Event Handler)。开发人员预先注册需要处理的事件及其事件处理器(或回调函数);事件分离器负责将请求事件传递给事件处理器。两个与事件分离器有关的模式是Reactor和Proactor。Reactor模式采用同步IO,而Proactor采用异步IO。
Reactor模式
在Reactor中,事件分离器负责等待文件描述符或socket为读写操作准备就绪,然后将就绪事件传递给对应的处理器,最后由处理器负责完成实际的读写工作。
Linux epoll 使用 Reactor 模式。Reactor 模式使用同步 I/O(一般来说)。Reactor 的标准(典型)的工作方式是:
- 注册读就绪事件和相应的事件处理器
- Reactor 等待事件
- 事件到来,Reactor 分发可读写事件(读写准备就绪)到用户事件处理函数
- 用户读取数据
- 用户处理数据
- 事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
与 Proactor 模式相比,Reactor 模式下,用户有责任在收到可读写事件后进行实际的 I/O 操作。
Proactor模式
而在Proactor模式中,处理器--或者兼任处理器的事件分离器,只负责发起异步读写操作。IO操作本身由操作系统来完成。传递给操作系统的参数需要包括用户定义的数据缓冲区地址和数据大小,操作系统才能从中得到写出操作所需数据,或写入从socket读到的数据。事件分离器捕获IO操作完成事件,然后将事件传递给对应处理器。比如,在windows上,处理器发起一个异步IO操作,再由事件分离器等待IOCompletion事件。典型的异步模式实现,都建立在操作系统支持异步API的基础之上,我们将这种实现称为“系统级”异步或“真”异步,因为应用程序完全依赖操作系统执行真正的IO工作。
Windows IOCP 使用 Proactor 模式。Proactor 模式使用异步 I/O。Proactor 的标准(典型)的工作方式是:
- 处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这种情况下,处理器无视IO就绪事件,它关注的是完成事件。
- 事件分离器等待操作完成事件
- 在分离器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分离器读操作完成。
- 事件分离器呼唤处理器。
- 事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分离器。
Proactor 模式下,用户在调用异步 I/O 时会传递一个 Buffer 给系统,系统进行实际的 I/O 操作并从传递给系统的 Buffer 中获取或者放入数据。
Reactor V.S. Proactor
可以看出,两个模式的相同点,都是对某个IO事件的事件通知(即告诉某个模块,这个IO操作可以进行或已经完成)。在结构上,两者也有相同点:demultiplexor负责提交IO操作(异步)、查询设备是否可操作(同步),然后当条件满足时,就回调handler;
不同点在于,异步情况下(Proactor),当回调handler时,表示IO操作已经完成(数据已从系统内核拷贝到程序内存);同步情况下(Reactor),回调handler时,表示IO设备可以进行某个操作(can read or can write,数据准备就绪,但是用户需要自己将数据从系统内核拷贝到程序内存)。