【TinyWebServer】02半同步半反应堆线程池

发布时间 2023-09-07 17:46:15作者: -zx-

本篇主要围绕服务器项目中涉及的知识进行介绍,详细可参考《Linux下高性能服务器编程》。

服务器编程基本框架

主要由I/O单元,逻辑单元和网络存储单元组成,其中每个单元之间通过请求队列进行通信,从而协同完成任务。

I/O单元:用于处理客户端连接,读写网络数据;

逻辑单元:用于处理业务逻辑的线程;

网络存储单元:本地的数据库和本地文件等。

image

五种I/O模型

  • 阻塞IO:调用者必须调用了某个函数,等待这个函数返回,期间什么也不做,不停去轮询检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作
  • 非阻塞IO:非阻塞等待,每隔一段时间就去检测IO事件是否。没有就绪就可以做其他事。非阻塞IO执行调用总是立即返回,不管事情是否已经发生,若没有发生,直接返回-1,此时可以根据error区分这两种情况,对于accept,recv和send,事情未发生时,errno通常被设置成eagain。
  • 信号驱动IO:Linux用套接口进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO时间就绪,进程收到SIGIO信号。然后处理IO事件。
  • IO复用:Linux提供了select/poll函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的使这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。知道有数据可读或者可写时,才真正的调用IO操作函数。
  • 异步IO:Linux中,可以调用aio_read函数噶搜内核描述子缓冲区指针和缓冲区的大小、文件偏移及通知方式,然后立即返回,当内核将数据拷贝到缓冲区后,在通知应用程序。

注意:阻塞I/O,非阻塞I/O,信号驱动I/O和IO复用都i是同步I/O。同步I/O是指内核向应用程序通知的是就绪事件,比如只通知有客户端连接,要求用户代码自行执行I/O操作,异步I/O是指内核向应用程序通知的是完成事件,比如读取客户端的数据后才通知应用程序,由内核完成I/O操作。

事件处理模式

  • Reactor模式中,主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话立刻通知工作线程(逻辑处理单元),读写数据、接受新连接及处理客户请求均在工作线程中完成。通常由同步I/O实现。
  • Proactor模式中,主线程和内核负责处理读写数据、接受新连接等I/O操作,工作线程仅负责业务逻辑,如处理客户请求。通常由异步I/O实现。

同步I/O模拟Proactor模式

由于异步I/O并不成熟,实际中使用较少,所以可以采用同步I/O模拟实现Proactor模式。

同步I/O模型的工作流程如下:

  • 主线程往epoll内核事件表注册socket上的读就绪事件。
  • 主线程调用epoll_wait等待socket上有数据可读
  • 当socket上有数据可读,epoll_wait通知主线程,主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。
  • 睡眠在请求队列上某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件
  • 主线程调用epoll_wait等待socket可写。
  • 当socket上有数据可写,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果。

并发编程模式

并发编程方法的实现有多线程和多进程两种,这里涉及的并发模式指的是I/O处理的与逻辑单元协同完成任务的方法。

  • 半同步/半异步模式
  • 领导者/追随者模式

半同步/半反应堆

半同步/半反应堆并发模式是半同步/半异步的变体,将半异步具体化为某种事件处理模式。

并发模式中的同步和异步

  • 同步指的是程序完全按照代码序列的顺序执行
  • 异步指的是程序的执行需要由系统事件驱动

半同步/半异步模式工作流程

  • 同步线程用于处理客户逻辑
  • 异步线程用于处理I/O事件
  • 异步线程监听到客户的请求后,就将其封装成请求对象并插入请求队列中(这个请求对象具体是按照什么协议格式封装的呢? -- 没有固定格式,实现是使用的模板,实际调用时使用的是http_conn *users对象)
  • 请求队列将通知某个工作在同步模式的工作线程来读取并处理该请求对象(用工作线程去处理队列中的待处理对象)

半同步/半反应堆工作流程(以Proactor模式为例)

  • 主线程充当异步线程,负责监听所有socket上的事件
  • 如果有新的请求到来,主线程接收得到一个新的连接socket,然后往epoll内核事件表中注册该socket上的读写事件
  • 如果连接socket上有读写事件发生,主线程从socket上接收数据,并将数据封装成请求对象插入到请求队列中
  • 所有工作线程睡眠在请求队列上,当有任务到来时,通过竞争(如互斥锁)获得任务的接管权

线程池

  • 空间换时间,浪费服务器的硬件资源,换取运行效率。
  • 池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化,称为静态资源
  • 当服务器进入正式运行阶段,开始处理客户请求的时候,如果他需要相关的资源可以直接从池中获取,无需动态分配
  • 当服务器处理完一个客户连接后,可以把相关的资源放到池中,无需执行系统调用释放资源











转载文章:

https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=4&sn=caa323faf0c51d882453c0e0c6a62282&chksm=83ffbefeb48837e841a6dbff292217475d9075e91cbe14042ad6e55b87437dcd01e6d9219e7d&scene=0&xtrack=1#rd