目前存在的线程模型有:
- 传统阻塞I/O服务模型
- Reactor模式
根据Reactor的数量和处理资源池线程的数量不同,有3种典型的实现:
- 单Reactor单线程
- 单Reactor多线程
- 主从Reactor多线程
先描述一下传统阻塞I/O模型以示区别。
传统阻塞I/O模型
-
原理图
其中,蓝色框表示线程;黄色框表示对象;绿色框表示方法。 -
模型特点
1)一个客户端对应一个线程,每个线程完成数据的输入、业务处理和数据返回;
2)当并发数很大时会创建大量线程,占用很大的系统资源;
3)创建连接后,如果当前线程没有数据可读,该线程会阻塞在read操作,造成线程资源浪费。 -
简单改进
在I/O阻塞模型下,为防止大量用户连接服务器从而创建大量线程的情况,做一个简单改进——采用线程池。该方案可以将线程数量人为控制在指定范围内,但不能解决根本问题。考虑如下情况:由于读写操作会阻塞线程,假设当线程池内所有线程都被阻塞在读写操作上,而后又有大量客户端创建连接请求,此时会加入阻塞队列,当阻塞队列加满后会执行拒绝策略,则大量用户会连接超时。
单Reactor单线程
-
原理图
-
方案说明
1)select监听客户端的请求事件,收到事件后通过Dispatch进行分发;
2) 如果是建立连接请求事件,则由Acceptor通过accept方法处理连接请求,然后创建一个Handler对象处理连接后的后续业务;
3)如果不是连接请求事件,则Reactor会分发调用连接对应的Handler来响应;
4)Handler处理的完整流程为:read→业务处理→send。 -
方案优缺点
1)优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程种完成;
2)缺点:只有一个线程无法发挥多核CPU的性能;当Handler在处理某个业务时,整个进程无法处理其他连接事件,容易造成性能瓶颈;可靠性差,若线程意外终止或进入死循环,会导致整个通信模块不可用。
3)使用场景:客户端数量有限,业务处理非常快速,比如Redis业务处理的时间复杂度O(1)的情况。
单Reactor多线程
-
原理图
-
方案说明
1)Reactor对象通过select监控客户端请求事件,收到事件后,通过dispatch进行分发;
2)如果是建立连接请求,则Acceptor通过调用accept方法处理连接请求,然后创建一个Handler对象处理连接后的各种事件;
3)如果不是连接请求,则由Reactor分发调用对应的Handler来处理;
4)Handler只负责响应事件,不做具体的业务处理,通过read读取数据后会分发给后面的Worker线程池的线程处理业务;
5)Worker线程池会分配独立线程完成真正的业务,并将结果返回给Handler;
6)Handler收到响应后,通过send将结果返回给Client。 -
方案优缺点
1)优点:可以充分利用多核CPU的处理能力;
2)缺点:多线程数据共享和访问比较复杂,Reactor处理所有的事件的监听和响应,在单线程运行,在高并发的场景容易出现性能瓶颈。
主从Reactor多线程
- 原理图
针对单Reactor多线程模型中,Reactor在单线程中运行,高并发场景下容易造成性能瓶颈,可以让Reactor在多线程中运行。 - 方案说明
1)Reactor主线程MainReactor对象通过select监听连接事件,收到事件后通过Acceptor处理连接事件;
2)当Acceptor处理连接事件后,MainReactor将连接分配给SubReactor;
3)SubReactor将连接队列进行监听,并创建handler进行各种事件处理,当有新事件发生时,subreactor就会调用对应的handler处理;
4)handler通过read读取数据,分发给后面的worker线程池处理;
5)worker线程池分配独立的线程worker进行业务处理,并返回结果;
6)handler收到响应的结果后,再通过send方法将结果返回给client;
7)Reactor主线程可以对应多个Reactor子线程,即MainReactor可以关联多个SubReactor。