先明白基本概念:
- 同步/异步:区别在于返回结果有没有立即返回。
- 阻塞/非阻塞:区别在于进程/线程有没有挂起。
传统IO阻塞模型
模型特点
- 采用阻塞 IO 模式获取输入的数据
- 每个连接都需要独立的线程完成数据的输入,业务处理,数据返回
问题
- 当并发数很大,就会创建大量的线程,占用很大系统资源
- 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在 read 操作,造成线程资源浪费
解决方法:
第一种:IO多路复用,多个事件阻塞在一个进程/线程中
Reactor模式
也叫 Dispatcher 模式。
I/O 多了复用统一监听事件,收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器的必备技术之一。
单reactor单线程
方案说明:
-
Select 是前面 I/O 复用模型介绍的标准网络编程 API,可以实现应用程序通过一个阻塞对象监听多路连接请求
-
Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发
-
如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理
-
如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应
-
Handler 会完成 Read→ 业务处理 →Send 的完整业务流程
多reactor多线程
注意:建立连接和socket里有数据可写可读都是一个就绪事件。
方案详细说明如下:
- 父进程中mainReactor对象通过select监控连接建立事件,收到事件后通过Acceptor接收,将新的连接分配给某个子进程。
- 子进程的subReactor将mainReactor分配的连接加入连接队列进行监听,并创建一个Handler用于处理连接的各种事件。
- 当有新的事件发生时,subReactor会调用连接对应的Handler(即第2步中创建的Handler)来进行响应。
- Handler完成read→业务处理→send的完整业务流程。
多Reactor多进程/线程的方案看起来比单Reactor多线程要复杂,但实际实现时反而更加简单,主要原因是:
- 父进程和子进程的职责非常明确,父进程只负责接收新连接,子进程负责完成后续的业务处理。
- 父进程和子进程的交互很简单,父进程只需要把新连接传给子进程,子进程无须返回数据。
- 子进程之间是互相独立的,无须同步共享之类的处理(这里仅限于网络模型相关的select、read、send等无须同步共享,“业务处理”还是有可能需要同步共享的)。
目前著名的开源系统Nginx采用的是多Reactor多进程,采用多Reactor多线程的实现有Memcache和Netty。
Proactor
Reactor是非阻塞同步网络模型,因为真正的read和send操作都需要用户进程同步操作。这里的“同步”指用户进程在执行read和send这类I/O操作的时候是同步的,如果把I/O操作改为异步就能够进一步提升性能,这就是异步网络模型Proactor。
Reactor可以理解为“来了事件我通知你,你来处理”,而Proactor可以理解为“来了事件我来处理,处理完了我通知你”。这里的“我”就是操作系统内核,“事件”就是有新连接、有数据可读、有数据可写的这些I/O事件,“你”就是我们的程序代码。
Reactor实现了一个被动的事件分离和分发模型,服务等待请求事件的到来,再通过不受间断的同步处理事件,从而做出反应;
Proactor实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量。
所以涉及到文件I/O或耗时I/O可以使用Proactor模式,或使用多线程模拟实现异步I/O的方式。
Reference
https://zhuanlan.zhihu.com/p/95662364
https://ld246.com/article/1594887244486
https://leeshengis.com/archives/8805
https://cloud.tencent.com/developer/article/1769945
[1] https://www.cnblogs.com/goodcandle/archive/2005/12/10/294652.html
[2] https://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html
网友评论