知识卡片
单Reactor单进程与单Reactor多线程的取舍
内容
[[I/O多路复用的由来:从资源池到事件通知机制]]结合线程池后诞生了Reactor模式,本质是”来了事件我通知你,你来处理”——Reactor负责用select/epoll等机制统一监听所有连接上的事件,一旦有事件就通过dispatch分发给对应的处理逻辑(这也是Reactor又被叫做Dispatcher模式的原因)。Reactor模式的具体实现要看两个维度的组合:Reactor本身的数量(单个还是多个)、处理资源池的数量(单进程/线程还是多进程/线程),理论上有四种组合,但”多Reactor单进程”既复杂又没有性能优势,实践中不存在,真正落地的只有三种典型方案。第一种是单Reactor单进程/线程:Reactor通过select监听事件并dispatch,连接建立事件交给Acceptor处理(accept连接并创建对应的Handler),其余事件交给对应连接的Handler处理,Handler完整走完read→业务处理→send整个流程,全过程都在一个进程/线程里完成,好处是简单,没有进程间通信、没有资源竞争;坏处也很直接——单进程发挥不出多核CPU的性能(想用多核只能在一台机器上部署多套系统,运维复杂度陡增),而且Handler正在处理某个连接的业务时,整个进程没法处理其他连接的事件,很容易撞上性能瓶颈,因此这个方案只适合业务处理本身极快的场景,著名的开源实现是Redis(C语言系统一般用单Reactor单进程,Java系统因为JVM本身就是一个进程、内部本来就是多线程,一般用单Reactor单线程)。第二种是单Reactor多线程:为了发挥多核性能,把”业务处理”从Reactor所在的主线程里剥离出去——Handler只负责响应事件、读取数据,真正的业务处理交给独立的子线程(Processor)去做,处理完把结果传回主线程的Handler,再由Handler通过send返回给客户端;这个方案确实能利用多核,但也带来两个新问题:多线程间共享数据的互斥和保护变复杂了(比如Java NIO里Selector本身线程安全,但selectKeys()返回的键集合并非线程安全,处理时必须单线程或加同步保护);而且Reactor本身仍然只跑在主线程里、独自承担全部事件的监听和分发,遇到瞬间高并发依然会成为瓶颈。这里没有对应的”单Reactor多进程”方案,原因在于子进程完成业务处理后要把结果传回父进程、再由父进程发给对应client,这件事本身很麻烦——父子进程通信不是一条天然的”连接”,要模拟成连接纳入Reactor监听会相当复杂;而多线程天然共享数据,线程间传递结果虽然要处理同步问题,但复杂度比进程间通信低得多,这正是”单Reactor多进程”没被实际采用的根本原因。
结构图:
flowchart TB
A["Reactor模式:I/O多路复用统一监听事件+dispatch分发"]
A --> B["单Reactor单进程/线程"]
B --> B1["优点:简单,无进程间通信/无资源竞争"]
B --> B2["缺点:发挥不出多核CPU<br/>处理某连接时无法处理其他连接事件"]
B2 --> B3["适用:业务处理极快场景,如Redis"]
A --> C["单Reactor多线程"]
C --> C1["Handler只负责收发,业务处理交给独立子线程Processor"]
C1 --> C2["优点:能利用多核CPU"]
C1 --> C3["缺点:多线程共享数据需同步保护<br/>Reactor仍单点,瞬间高并发时是瓶颈"]
A --> D["为何没有'单Reactor多进程'方案?<br/>子进程结果传回父进程再转发,父子间不是天然连接<br/>模拟成连接纳入监听过于复杂,远不如线程间天然共享数据简单"]