当前位置: 首页 > news >正文

【C++高并发服务器WebServer】-18:事件处理模式与线程池

在这里插入图片描述

本文目录

  • 一、事件处理模式
    • 1.1 Reactor模式
    • 1.2 Proactor模式
    • 1.3 同步IO模拟Proactor模式
  • 二、线程池

一、事件处理模式

服务器程序通常需要处理三类事件:I/O事件、信号、定时事件。

对应的有两种高效的事件处理模式:Reactor和Proactor,同步I/O通常用于Reacotr模式,异步I/O模型用于实现Proactor模式。

1.1 Reactor模式

要求主线程(I/O处理单元)只负责监听文件描述符上是否有事发生,有的话就立即将该事件通知工作线程(逻辑单元),将socket可读可写事件放入请求队列,交给工作线程处理。除此之外,主线程不做任何其他实质性的工作。读写数据、接受新的连接、处理客户端请求均在工作线程中完成。

使用同步I/O(以epoll_wait为例子)实现的Reactor工作流程如下:

1、主线程往 epoll内核事件表中注册socket上的读就绪事件。
2、主线程调用epoll_wait,等待socket上有数据可读。
3、当socket上有数据可读时候,epoll_wait通知主线程,主线程将socket可读事件放入请求队列中。
4、睡眠在请求队列上的某个工作线程被唤醒,从socket读取数据,并处理客户的请求,然后往 epoll内核事件表中注册该socket上的写就绪事件。
5、主线程调用epoll_wait等待socket可写。
6、当socket可写时,epoll_wait通知主线程,主线程将socket可写事件放入请求队列。
7、睡眠在请求队列上的某个工作线程被唤醒,往socket上写入服务器处理客户请求的结果。

在这里插入图片描述

1.2 Proactor模式

Proactor 模式将所有 I/O 操作都交给主线程和内核来处理(进行读、写),工作线程仅仅负责业务逻辑。使用异步 I/O 模型(以 aio_read 和 aio_write 为例)实现的 Proactor 模式的工作流程如下:

1.主线程调用 aio_read 函数向内核注册 socket 上的读完成事件,并告诉内核用户读缓冲区的位置以及读操作完成时如何通知应用程序(这里以信号为例)。
2.主线程继续处理其他逻辑。
3.当 socket 上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据已经可用。
4.应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求后,调用 aio_write 函数向内核注册 socket 上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序。
5.主线程继续处理其他逻辑。
6.当用户缓冲区的数据被写入 socket 之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕。
7.应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭 socket。

在这里插入图片描述

1.3 同步IO模拟Proactor模式

使用同步 I/0 方式模拟出 Proactor 模式。原理是:主线程执行数据读写操作,读写完成之后,主线程向工作线程通知这一"完成事件”。那么从工作线程的角度来看,它们就直接获得了数据读写的结果,接下来要做的只是对读写的结果进行逻辑处理。

使用同步 I/0 模型(以 epoll _wait为例)模拟出的 Proactor 模式的工作流程如下:
1.主线程往 epoll 内核事件表中注册 socket 上的读就绪事件。
2. 主线程调用 epoll _wait 等待 socket 上有数据可读。
3.当 socket 上有数据可读时,epoll wait 通知主线程。主线程从 socket 循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。
4.睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往 epoll 内核事件表中注册 socket 上的写就绪事件。
5.主线程调用 epoll wait 等待 socket 可写,
6.当 socket 可写时,epoll_wait 通知主线程。主线程往 socket 上写入服务器处理客户请求的结果。

在这里插入图片描述

二、线程池

线程池是由服务器预先创建的一组子线程,线程池中的线程数量应该和 CPU 数量差不多。线程池中的所有子线程都运行着相同的代码。当有新的任务到来时,主线程将通过某种方式选择线程池中的某一个子线程来为之服务。相比与动态的创建子线程,选择一个已经存在的子线程的代价显然要小得多。至于主线程选择哪个子线程来为新任务服务,则有多种方式:

1、主线程使用某种算法来主动选择子线程。最简单、最常用的算法是随机算法和 Round Robin(轮流选取)算法,但更优秀、更智能的算法将使任务在各个工作线程中更均匀地分配,从而减轻服务器的整体压力。

2、主线程和所有子线程通过一个共享的工作队列来同步,子线程都睡眠在该工作队列上。当有新的任务到来时,主线程将任务添加到工作队列中。这将唤醒正在等待任务的子线程,不过只有一个子线程将获得新任务的“接管权",它可以从工作队列中取出任务并执行之,而其他子线程将继续睡眠在工作队列上。

在这里插入图片描述

线程池中的线程数量最直接的限制因素是中央处理器(CPU)的处理器(processors/cores)的数量N:如果你的CPU是4-cores的,对于CPU密集型的任务(如视频剪辑等消耗CPU计算资源的任务)来说,那线程池中的线程数量最好也设置为4(或者+1防止其他因素造成的线程阻塞);对于IO密集型的任务,一般要多于CPU的核数,因为线程间竞争的不是CPU的计算资源而是IO,IO的处理般较慢,多于cores数的线程将为CPU争取更多的任务,不至在线程处理10的过程造成CPU空闲导致资源浪费。

http://www.lryc.cn/news/535751.html

相关文章:

  • 23种设计模式的定义和应用场景-02-结构型模式-C#代码
  • 数据脱敏方案总结
  • 自然语言处理NLP入门 -- 第二节预处理文本数据
  • 02.10 TCP之文件传输
  • 基于STM32的ADS1230驱动例程
  • Bro想要玩github api
  • idea插件开发,如何获取idea设置的系统语言
  • 怎麼使用靜態住宅IP進行多社媒帳號管理
  • InfiniBand与IP over InfiniBand(IPOIB):实现高性能网络通信的底层机制
  • 掌握 PHP 单例模式:构建更高效的应用
  • 实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)
  • Python入门全攻略(四)
  • Ubuntu 22.04 - OpenLDAP安装使用(服务器+LAM+客户端)
  • Linux ARM64 将内核虚拟地址转化为物理地址
  • 使用 Visual Studio Code (VS Code) 开发 Python 图形界面程序
  • 图像处理篇---基本OpenMV图像处理
  • 一文讲清springboot所有注解
  • pytest测试专题 - 1.1 运行pytest
  • Java多线程——线程池的使用
  • NO.15十六届蓝桥杯备战|while循环|六道练习(C++)
  • DeepSeek 从入门到精通学习指南,2025清华大学《DeepSeek从入门到精通》正式发布104页pdf版超全解析
  • 2025年SEO自动优化工具
  • KEPServerEX 的接口类型与连接方式的详细说明
  • AGI时代的认知重塑:人类文明的范式转移与思维革命
  • OmniManip:以目标为中心的交互基元作为空间约束实现通用机器人操作
  • 论文第二次阅读笔记
  • 【Android开发AI实战】选择目标跟踪基于opencv实现——运动跟踪
  • 系统漏洞扫描服务:安全风险识别与防护指南
  • 2.Excel:滨海市重点中学的物理统考考试情况❗(15)
  • 使用 React 16+Webpack 和 pdfjs-dist 或 react-pdf 实现 PDF 文件显示、定位和高亮