TinywebServer代码详解-半同步半反应堆线程池(2)
TinywebServer代码详解–半同步半反应堆线程池(2)
该blog内容转自:最新版Web服务器项目详解 - 02 半同步半反应堆线程池(上)
该blog对上述内容进行补充(在本人的角度)
结合此前牛客项目记录的blog一起学习:牛客WebServer项目实战(点击跳转)
原项目地址(点击跳转)
博主添加注释后项目地址(点击跳转)
一、基础知识
1.服务器编程框架
服务器基本编程框架如下图:
模块 | 功能 |
---|---|
I/O 处理单元 | 处理客户连接,读写网络数据 |
逻辑单元 | 业务进程或线程 |
网络存储单元 | 数据库、文件或缓存 |
请求队列 | 各单元之间的通信方式 |
- IO处理单元:服务器管理客户连接的模块。它通常要完成以下工作:等待并接受新的客户连接,接收客户数据,将服务器响应数据返回给客户端。但是数据的收发不一定在 I/O 处理单元中执行,也可能在逻辑单元中执行,具体在何处执行取决于事件处理模式
- 逻辑单元:一个逻辑单元通常是一个进程或线程。它分析并处理客户数据,然后将结果传递给
I/O
处理单元或者直接发送给客户端(具体使用哪种方式取决于事件处理模式)。服务器通常拥有多个逻辑单元,以实现对多个客户任务的并发处理 - 网络存储单元:可以是数据库、缓存和文件,但不是必须的
- 请求队列:请求队列是各单元之间的通信方式的抽象。
I/O
处理单元接收到客户请求时,需要以某种方式通知一个逻辑单元来处理该请求。同样,多个逻辑单元同时访问一个存储单元时,也需要采用某种机制来协调处理竞态条件。请求队列通常被实现为池(线程池/进程池)的一部分
2.五种I/O模型
查看此前学习牛客课程的Blog
:牛客WebServer项目实战(点击跳转)
查看此前学习Blog
:TinywebServer相关基础知识(1)(点击跳转)
3.事件处理模式
查看此前学习牛客课程的Blog
:牛客WebServer项目实战(点击跳转)
查看此前学习Blog
:TinywebServer相关基础知识(1)(点击跳转)
(1)同步I/O模拟Proactor模式
使用同步I/O
方式模拟出 Proactor
模式,原理是:主线程执行数据读写操作,读写完成之后,主线程向工作线程通知这一”完成事件“。那么从工作线程的角度来看,它们就直接获得了数据读写的结果,接下来要做的只是对读写的结果进行逻辑处理
仔细思考:异步I/O,应用程序只需要发起I/O操作,不需要等待I/O操作完成,I/O操作完成(数据准备)交给内核完成,内核将数据准备好,并且将数据从内核空间拷贝到用户空间,进而通过相关机制通知应用程序,数据准备好了,I/O操作完成后,主线程向工作线程通知这一个完成事件,进而工作线程对准备好的数据进行分析处理,那么从工作线程的角度来看,它们就直接获得了数据读写的结果
使用同步I/O
模型(以 epoll_wait
为例)模拟出的Proactor
模式的工作流程如下:
- 主线程往
epoll
内核事件表中注册socket
上的读就绪事件- 主线程调用
epoll_wait
等待 socket 上有数据可读- 当
socket
上有数据可读时,epoll_wait
通知主线程。主线程从socket
循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列- 睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往
epoll
内核事件表中注册socket
上的写就绪事件- 主线程调用
epoll_wait
等待socket
可写- 当
socket
可写时,epoll_wait
通知主线程。主线程往socket
上写入服务器处理客户请求的结果
图示如下:
从上文的仔细思考部分,可以如下:
主线程负责epoll
实例中的文件描述符监听,以及IO
的读写操作;而工作线程仅仅负责业务处理逻辑
4.并发编程模式
并发编程方法的实现有多线程和多进程两种,但这里涉及的并发模式指I/O处理单元与逻辑单元的协同完成任务的方法。
- 半同步/半异步模式
- 领导者/追随者模式
(1)半同步/半反应堆
半同步/半反应堆并发模式是半同步/半异步的变体,将半异步具体化为某种事件处理模式.
并发模式中的同步和异步:
- 同步指的是程序完全按照代码序列的顺序执行
- 异步指的是程序的执行需要由系统事件驱动
半同步/半异步模式工作流程:
- 同步线程用于处理客户逻辑
- 异步线程用于处理I/O事件
- 异步线程监听到客户请求后,就将其封装成请求对象并插入请求队列中
- 请求队列将通知某个工作在同步模式的工作线程来读取并处理该请求对象
半同步/半反应堆工作流程(以Proactor
模式为例):
如:第三部分的:同步I/O
模拟Proactor
模式
- 主线程充当异步线程,负责监听所有socket上的事件
- 若有新请求到来,主线程接收之以得到新的连接socket,然后往epoll内核事件表中注册该socket上的读写事件
- 如果连接socket上有读写事件发生,主线程从socket上接收数据,并将数据封装成请求对象插入到请求队列中
- 所有工作线程睡眠在请求队列上,当有任务到来时,通过竞争(如互斥锁)获得任务的接管权
二、线程池
- 空间换时间,浪费服务器的硬件资源,换取运行效率.
- 池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化,这称为静态资源.
- 当服务器进入正式运行阶段,开始处理客户请求的时候,如果它需要相关的资源,可以直接从池中获取,无需动态分配.
- 当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用释放资源.