文章目录
- Reactor模式
- Proactor模式
- 同步I/O模型模拟Proactor模式
- 两者的优缺点
- Reactor
- Proactor
同步I/O模型通常用于实现 Reactor
模式,异步I/O模型通常用于实现 Proactor
模式。(不是绝对的,同步I/O也可模拟出 Proactor
模式)
Reactor模式
原理
Reactor
模式要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件就绪,如果有则将该就绪事件通知给工作线程(逻辑单元)。除此之外主线程不会进行其他实质性的工作,读写数据、接收新连接、业务逻辑处理全部在工作线程中完成。
工作流程
这里以 epoll_wait
为例,使用同步I/O模型实现的 Reactor
模式的工作流程如下:
- 主线程往
epoll
内核事件表中注册socket
上的读就绪事件。 - 主线程调用
epoll_wait
开始对socket
的读事件进行监控。 - 如果
socket
读就绪,epoll_wait
会通知主线程,主线程则将socket
可读事件(即socket
连接本身) 放入请求队列中。 - 请求队列上某个休眠的工作线程被唤醒,此时会从
socket
中读取数据,并且处理用户请求,然后往epoll
内核事件表中注册该socket
的写就绪事件。 - 主线程继续调用
epoll_wait
对socket
的写事件进行监控。 - 当
socket
写就绪时,epoll_wait
会通知主线程,主线程则将socket
可写事件(即socket
连接本身) 放入请求队列中。 - 请求队列上某个休眠的工作线程被唤醒,将服务器处理客户请求的结果写入到
socket
中
工作线程从请求队列中取出事件后,根据事件类型来决定如何处理事件,所以不会区分 读工作线程
和 写工作线程
。
Proactor模式
原理
Proactor模式则是将所有的I/O操作全部交给主线程和内核处理,工作线程仅仅负责业务逻辑。
工作流程
这里以 aio
为例,使用异步I/O模型实现的 Reactor
模式的工作流程如下:
- 主线程调用
aio_read
向内核注册socket
上的读完成事件,并且告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序(这里以信号为例)。 - I/O事件交给内核进行异步处理,此时主线程继续处理其他逻辑(区别于
Reactor
中主线程需要持续监控就绪事件)。 - 当
socket
上的数据已被读入用户缓冲区后,内核向应用程序发送一个信号,通知其数据已可用。 - 通过应用程序预先定义好的信号处理函数来选择一个工作线程以处理客户请求。
- 工作线程处理完客户请求之后会调用
aio_write
向内核注册socket
上的写就绪事件,并且告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序(仍选择使用信号)。 - 主线程继续处理其他逻辑(同2)。
- 当用户缓冲区的数据被写入
socket
后,内核向应用程序发送一个信号,通知其数据发送完成。 - 通过应用程序 注册(预先定义好) 的信号处理 事件(函数) 来选择一个工作线程来进行善后处理,例如是否关闭
socket
。
由于读/写事件是通过 aio_read/aio_write
向内核中进行 注册 的,并由内核通过 信号 向应用程序 报告 的。因此,不同于 Reactor
模式,Proactor
的 epoll_wait
仅仅用来监听 socket
上是否有新的连接请求到来,而不用于 注册 or 报告 读/写事件。
同步I/O模型模拟Proactor模式
原理
主线程执行数据读写操作,完成后向工作线程通知事件的完成。从工作线程的角度来看,他们就直接获得了数据读写的结果,接下来的工作就只需要对读写结果进行业务逻辑处理。
工作流程
这里以 epoll_wait
为例,使用同步I/O模型实现的 Proactor
模式的工作流程:
- 主线程往
epoll
内核事件表中注册socket
上的读就绪事件。(同步I/O注册就绪事件、异步I/O注册完成事件) - 主线程调用
epoll_wait
等待socket
上有数据可读。 - 当
socket
上有数据可读,epoll_wait
通知主线程,主线程从socket
中循环读取数据,直到没有数据可读。然后将读到的数据封装成一个请求对象插入请求队列中。 - 请求队列上某个休眠的工作线程被唤醒,此时它会获取请求对象并且处理客户请求,然后往
epoll
内核事件表中注册socket
的写就绪事件。 - 主线程调用
epoll_wait
等待socket
可写。 - 如果
socket
写就绪,epoll_wait
通知主线程,主线程往socket
中写入服务器处理客户请求的结果。
两者的优缺点
Reactor
Reactor
实现了一个被动的事件分离和分发模型:
- 主线程只负责监听读写事件是否就绪,就绪后放入请求队列,并唤醒请求队列上某个工作线程;
- 由工作线程读写数据并处理客户请求。
优点:
- 实现相对简单,对于耗时短的处理场景处理高效。
- 操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性。
- 事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁。
- 将与应用无关的多路分解、分配机制和与应用相关的回调函数分离开来。
缺点:
- 处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理。
适用场景:
同时接收多个服务请求,并且依次同步处理它们的事件驱动程序。
Proactor
Proactor
实现了一个主动的事件分离和分发模型:
- 主线程监听事件是否就绪;
- 内核执行I/O操作读写数据;
- 上一步完成后根据预先注册好的信号函数选择一个工作线程处理客户请求。
优点:
- 性能更高,能够适应耗时长的并发场景(各个任务间互不影响);
- 这种设计允许多个任务并发的执行,从而提高吞吐量。
缺点:
- 实现逻辑复杂,依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少。
- 实现优秀的如
windows IOCP
,但由于windows
系统用于服务器的局限性,目前应用范围较小。 - 而
Unix/Linux
系统对纯异步的支持有限,应用事件驱动的主流方案还是通过select/epoll
来实现。
- 实现优秀的如
适用场景:
异步接收、同时处理多个服务请求的事件驱动程序。