02 半同步半反应堆线程池

news2024/11/25 6:28:19

服务器编程基本框架

主要由I/O单元,逻辑单元和网络存储单元组成,其中每个单元之间通过请求队列进行通信,从而协同完成任务。
其中I/O单元用于处理客户端连接,读写网络数据;逻辑单元用于处理业务逻辑的线程;网络存储单元指本地数据库和文件等。
在这里插入图片描述

五种I/O模型

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

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

事件处理模式

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

同步IO模拟proactor模式

由于异步IO并不成熟,实际中使用较少,这里将使用同步IO模拟实现proactor模式。
同步IO模型的工作流程如下(epoll_wait为例):

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

并发编程模式

并发编程方法的实现有多线程和多进程两种,但这里的并发模式指IO处理单元与逻辑单元的协同完成任务的方法

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

半同步/半反应堆

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

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

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

  • 同步线程用于处理客户逻辑
  • 异步线程用于处理IO事件
  • 异步线程监听到客户请求后,将其封装成请求对象并插入请求队列中
  • 请求队列将通知某个工作在同步模式的工作线程来读取并处理请求对象

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

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

线程池

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

基础知识

静态成员变量

将类成员变量声明为static,则为静态成员变量,与一般的成员变量不同,无论建立多少对象,都只有一个静态成员变量的拷贝,静态成员变量属于一个类,所有对象共享。
静态变量在编译阶段就分配了空间,对象还没创建时就已经分配了空间,放到全局静态区。

  • 最好是类内声明,类外初始化(以免类名访问静态成员访问不到)
  • 无论公有,私有,静态成员都可以在类外定义,但私有成员仍有访问权限
  • 非静态成员类外不能初始化
  • 静态成员数据是共享的

静态成员函数

将类成员函数声明为static,则为静态成员函数

  • 静态成员函数可以直接访问静态成员变量,不能直接访问普通成员变量,但可以通过参数传递的方式访问
  • 普通成员函数可以访问普通成员变量,也可以访问静态成员变量
  • 静态成员函数没有this指针。非静态数据成员为对象单独维护,但静态成员函数为共享函数,无法区分是1哪个对象,因此不能直接访问普通变量成员,也没有this指针

pthread_create陷阱

#include <pthread.h>
int pthread_create(pthread_t *thread_tid,//返回新生成的线程的id
					const pthread_attr_t *attr,//指向线程属性的指针,通常设置为NULL
					void * (*start_routine) (void *),//处理线程函数的地址
					void *arg);//start_routine()中的参数

函数原型中的第三个参数,为函数指针,指向处理线程函数的地址。该函数,要求为静态函数。如果处理线程函数为类成员函数时,需要将其设置为静态成员函数

this指针的锅

pthread_create()的函数原型中第三个参数的类型为函数指针,指向的线程处理函数类型为(void *),若线程函数为类成员函数,则this指针会作为默认的参数被传进函数中,从而和线程函数参数不匹配,不能通过编译。
静态成员函数就没有这个问题,里面没有this指针。

线程池分析

线程池的设计模式为半同步/半反应堆,其中反应堆具体为Proactor事件处理模式。
主线程为异步线程,负责监听文件描述符,接收socket新连接,若当前监听的socket发生了读写事件,然后将任务插入到请求队列。工作线程从请求队列中取出任务,完成读写数据的处理。
使用一个工作队列完全解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务,工作线程通过竞争来取得任务并执行它。

  • 同步IO模拟Proactor模式
  • 半同步/半反应堆
  • 线程池

线程池定义

线程处理函数和运行函数设置为私有属性。

//线程池类,将它定义为模板类是为了代码复用,模板参数T是任务类
template<typename T>
class threadpool{
public:
    //thread_number是线程池中线程的数量
    //max_requests是请求队列中最多允许的、等待处理的请求数量
    //connPool是数据库连接池指针
    threadpool(connection_pool *connPool,int thread_number=8,int max_requests=10000);
    ~threadpool();

    //像请求队列中插入任务请求
    bool append(T* request);

private:
    /*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/
    static void* worker(void* arg);
    void run();
private:
    //线程的数量
    int m_thread_number;

    //描述线程池的数组,大小为m_thread_number
    pthread_t * m_threads;

    //请求队列中最多允许的、等待处理的请求的数量
    int m_max_requests;

    //请求队列
    std::list<T*> m_workqueue;

    //保护请求队列的互斥锁
    locker m_queuelocker;

    //是否有任务需要处理
    sem m_queuestat;

    //是否结束线程
    bool m_stop;

    //数据库连接池
    connection_pool *m_connPool;
};

线程池创建与回收

构造函数中创建线程池,pthread_create函数中将类的对象作为参数传递给静态函数worker,在静态函数中引入这个对象,并调用其动态方法run。
具体的,类对象传递时用this指针,传递给静态函数后,将其转换为线程池类,并调用成员函数run。

template<typename T>
threadpool<T>::threadpool(connection_pool *connPool,int thread_number,int max_requests):
        m_thread_number(thread_number),m_max_requests(max_requests),
        m_stop(false),m_threads(NULL),m_connPool(connPool){

    if((thread_number<=0)||(max_requests<=0)){
        throw std::exception();
    }  

    //创建线程池数组
    m_threads=new pthread_t[m_thread_number];
    if(!m_threads){
        throw std::exception();
    }

    //创建thread_number个线程,并将他们设置为脱离线程
    for(int i=0;i<thread_number;++i){
        printf("create the %dth thread\n",i);
        if(pthread_create(m_threads+i,NULL,worker,this)!=0){
            delete [] m_threads;
            throw std::exception();  
        }

        //pthread_detach():主线程与子线程分离,子线程结束后,资源自动回收
        //调用成功后返回0,其他任何返回值都表示出现了错误
        if(pthread_detach(m_threads[i])){
            delete [] m_threads;
            throw std::exception();
        }
    }   
}

向请求队列中添加任务

通过list容器创建请求队列,向队列中添加时,通过互斥锁保证线程安全,添加完成后通过信号量提醒有任务要处理,最后注意线程同步。

//向请求队列中添加任务
template<typename T>
bool threadpool<T>::append(T* request){
    //互斥锁保证线程安全
    m_queuelocker.lock();

    //根据硬件,预先设置请求队列的最大值
    if(m_workqueue.size()>m_max_requests){
        m_queuelocker.unlock();
        return false;
    }

    //添加任务
    m_workqueue.push_back(request);
    m_queuelocker.unlock();

    //信号量提醒有任务要处理
    m_queuestat.post();
    return true;
}

线程处理函数

内部访问私有成员函数run,完成线程处理要求。

template<typename T>
void* threadpool<T>::worker(void* arg){
    //将参数强转为线程池类,调用成员方法
    threadpool* pool=(threadpool*)arg;
    pool->run();
    return pool;
}

run执行任务

主要实现,工作线程从请求队列中取出某个任务进行处理,注意线程同步。

template<typename T>
void threadpool<T>::run(){
    while(!m_stop){
        //信号量等待
        m_queuestat.wait();

        //被唤醒后先加互斥锁
        m_queuelocker.lock();
        if(m_workqueue.empty()){
            m_queuelocker.unlock();
            continue;
        }

        //从请求队列中取出第一个任务
        //将任务从请求队列删除
        T* request=m_workqueue.front();
        m_workqueue.pop_front();
        m_queuelocker.unlock();

        if(!request) continue;

        //从连接池中取出一个数据库连接
        request->mysql=m_connPool->GetConnection();

        //process(模板类中的方法,这里是http类)进行处理
        request->process();

        //将数据库连接放回连接池
        m_connPool->ReleaseConnection(request->mysql);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/750751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ES7~ES13新特性(二)

1 ES7新增特性解析 2 ES8新增特性解析 3 ES10新增特性解析 4 ES11新增特性解析 5 ES12新增特性解析 6 ES13新增特性解析 ES8-对象相关的属性 ---entries的使用 const obj {name: "why",age: 18,height: 1.88,address: "广州市"}// 1.获取所有的keyco…

【NLP】Transformer模型原理(2)

接上文 【NLP】Transformer模型原理(1) 六、零层的transformer 观看涵盖与本节类似内容的视频:0 层理论 在进入更复杂的模型之前,简要考虑一下“零层”变压器很有用。这样的模型获取一个令牌,嵌入它,解嵌它以生成预测下一个令牌的对数: ​

音频数据分割单独处理后再拼接出现跳跃间断点的处理方法

+hezkz17进数字音频系统研究开发交流答疑 1如图所示 问题1: 对于一个81920字节的音频文件,如果是分割成小块4096输入(无重叠,均分),在频域上做去噪算法,每4k数据返回到时域上再拼接成80k的处理结果文件,发现处理结果有异常有跳跃间断点,像是频谱泄露?分割也需要有重…

mysql函数练习

创建表sch 向表中加入数据 1、创建一个可以统计表格内记录条数的存储函数 &#xff0c;函数名为count_sch() CREATE DEFINERroot% FUNCTION count_sch() RETURNS int(11) BEGINDECLARE total INT DEFAULT 0;#Routine body goes here...SELECT count(1) into total from sch;IN…

Linux->初识计算机网络

目录 前言&#xff1a; 1 网络发展背景 2 协议 2.1 网络协议初识 2.2 协议分层 2.3 OSI、TCP/IP层状模型 2.4 协议和操作系统的关系 2.5 根据协议栈的通信 3 网络中的地址管理 前言&#xff1a; 本篇当中没有任何关于网络编程的讲解&#xff0c;全部是对网络的宏观理解…

Go语言github.com/gorilla/websocket框架websocket协议通信实战

websocket是实际开发中比较常用的应用层协议&#xff0c;本文利用github.com/gorilla/websocket框架进行websocket通信实战。 目录 1.下载github.com/gorilla/websocket 2.websocket服务端 3.websocket Go客户端 4.websocket 网页客户端 5.运行结果展示 1.下载github.com…

【UI自动化测试】appium+python+unittest+HTMLRunner

进阶Python接口自动化测试必备教程&#xff08;2023全网最详细&#xff09; 简介 获取AppPackage和AppActivity 定位UI控件的工具 脚本结构 PageObject分层管理 HTMLTestRunner生成测试报告 启动appium server服务 以python文件模式执行脚本生成测试报告 下载与安装 下载需要自…

深度学习(28)——YOLO系列(7)

深度学习&#xff08;28&#xff09;——YOLO系列&#xff08;7&#xff09; 咱就是说&#xff0c;需要源码请造访&#xff1a;Jane的GitHub&#xff1a;在这里 上午没写完的&#xff0c;下午继续&#xff0c;是一个小尾巴。其实上午把训练的关键部分和数据的关键部分都写完了…

macOS 怎么安装redis数据库

1 访问redis数据库下载网址 http://download.redis.io/releases/ 访问上述的redis下载的网址&#xff0c;确定你想要的版本 然后下载即可 &#xff08;我选则的是6.2.6&#xff09; 然后下载 下载后 把这个文件解压&#xff0c;放在自己想要放在的位置 2 打开终端 输入对应的…

Hadoop 单机部署和测试(一)

Hadoop单机部署和测试 一.单机部署1.安装 JDK&#xff08;JDK11&#xff09;2.安装 HADOOP3.测试 一.单机部署 系统版本&#xff1a;cat /etc/anolis-release1.安装 JDK&#xff08;JDK11&#xff09; #!/bin/bashTOP_PATH$(pwd) JAVA_PATH/usr/local/java FILEls $TOP_PATH/…

本地部署 Stable Diffusion XL Gradio Demo WebUI

StableDiffusion XL Gradio Demo WebUI 0. 先展示几张 StableDiffusion XL 生成的图片1. 什么是 Stable Diffusion XL Gradio Demo WebUI2. Github 地址3. 安装 Miniconda34. 创建虚拟环境5. 安装 Stable Diffusion XL Gradio Demo WebUI6. 启动 Stable Diffusion XL Gradio De…

【LeetCode周赛】2022上半年题目精选集——动态规划

文章目录 2140. 解决智力问题解法1——倒序DP&#xff08;填表法&#xff09;解法2——正序DP&#xff08;刷表法&#xff09;⭐⭐⭐ 2167. 移除所有载有违禁货物车厢所需的最少时间⭐⭐⭐解法1——前缀和⭐⭐⭐⭐⭐解法2——前后缀分解 动态规划代码1——看了思路之后自己写的…

java中json和对象之间相互转换的运用

1.目录结构 2.配置相关文件 2.1.引入相关的pom文件 pom.xml <dependencies><!-- JSON --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.3</vers…

linux 基于debian_ubuntu AB系统适配(三)- overlayroot

Overlayroot Overlayroot是一个实用工具,允许您创建一个只读的根文件系统和一个可写的覆盖文件系统。这对于创建一个更安全和稳定的系统很有用,因为对系统所做的任何更改都将存储在覆盖文件系统中,可以很容易地丢弃或重置。 在Debian下,分离的系统在/userdata/rootfs_ove…

浅谈OS命令注入漏洞(Shell注入漏洞)

一、什么是OS命令注入&#xff1f; 1. 基本概念 OS&#xff08;Operating system&#xff09;命令注入&#xff08;也称为 Shell 注入&#xff09;是一个 Web 安全漏洞&#xff0c;允许攻击者在运行应用程序的服务器上执行任意操作系统 &#xff08;OS&#xff09; 命令&#…

考虑充电负荷空间可调度特性的分布式电源与电动汽车充电站联合配置方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

DaVinci Resolve Studio 18对Mac和Windows系统的要求

DaVinci Resolve Studio 18 是一款功能强大的专业视频编辑和调色软件&#xff0c;它提供了全面的工具和功能&#xff0c;让用户能够完成从剪辑、调色到特效和音频处理等各个方面的任务。DaVinci Resolve Studio 18 在中文界面上进行了优化&#xff0c;使得中文用户能够更加方便…

【xxl-job】分布式任务调度系统xxl-job搭建

XXL-JOB是一个轻量级分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展、开箱即用。 更多介绍&#xff0c;请访问官网&#xff1a;分布式任务调度平台XXL-JOB 一、任务调度中心(基于docker)【Version 2.4.0】 前提条件&#xff1a;任务调度…

Vue3 Vite electron 开发桌面程序

Electron是一个跨平台的桌面应用程序开发框架&#xff0c;它允许开发人员使用Web技术&#xff08;如HTML、CSS和JavaScript&#xff09;构建桌面应用程序&#xff0c;这些应用程序可以在Windows、macOS和Linux等操作系统上运行。 Electron的核心是Chromium浏览器内核和Node.js…

个体化治疗策略:如何使用机器学习定制化药物?

一、引言 个体化治疗策略是一种基于患者个体特征和病情的定制化治疗方法&#xff0c;旨在提高治疗效果、减少药物副作用并优化患者的生命质量。传统的治疗方法往往采用标准化的治疗方案&#xff0c;忽视了个体差异和患者特定的需求。然而&#xff0c;每个患者的基因组、疾病特征…