一文搞懂系列——Linux C线程池技术
背景
最近在走读诊断项目代码时,发现其用到了线程池技术,感觉耳目一新。以前基本只是听过线程池,但是并没有实际应用。对它有一丝的好奇,于是趁这个机会深入了解一下线程池的实现原理。
线程池的优点
线程池出现的背景,其实对应CPU性能优化——“瑞士军刀“文章中提到的短时应用。
即短时间内通过创建线程处理大量请求,但是请求业务的执行时间过短,会造成一些缺陷。
- 浪费系统资源。比如我们创建一个线程,再销毁一个线程耗时10ms,但是业务的执行时间只有10ms。这就导致系统有效利用率较低。
- 系统不稳定。如果短时间内,来了大量的请求,每一个请求都通过创建线程的方式执行。可能存在瞬时负载很高,请求响应降低,从而导致系统不稳定。
于是我们可以通过线程池技术,减少线程创建和消耗的耗时,提高系统的资源利用;控制线程并行数量,确保系统的稳定性;
线程池实现
线程池的核心包括以下内容:
- 线程池任务节点结构。
- 线程池控制器。
- 线程池的控制流程。
线程池任务节点结构
线程池任务结点用来保存用户投递过来的的任务,并放入线程池中的线程来执行,任务结构如下:
struct worker_t {void * (* process)(void * arg); /*回调函数*/int paratype; /*函数类型(预留)*/void * arg; /*回调函数参数*/struct worker_t * next; /*链接下一个任务节点*/
};
线程池控制器
线程池控制器用来对线程池进行控制管理,描述当前线程池的最基本信息,包括任务的投递,线程池状态的更新与查询,线程池的销毁等,其结构如下:
/*线程控制器*/
struct CThread_pool_t {pthread_mutex_t queue_lock; /*互斥锁*/pthread_cond_t queue_ready; /*条件变量*/worker_t * queue_head; /*任务节点链表 保存所有投递的任务*/int shutdown; /*线程池销毁标志 1-销毁*/pthread_t * threadid; /*线程ID*/int max_thread_num; /*线程池可容纳最大线程数*/int current_pthread_num; /*当前线程池存放的线程*/int current_pthread_task_num; /*当前已经执行任务和已分配任务的线程数目和*/int current_wait_queue_num; /*当前等待队列的的任务数目*/int free_pthread_num; /*线程池允许最大的空闲线程数/*//*** function: ThreadPoolAddWorkUnlimit* description: 向线程池投递任务* input param: pthis 线程池指针* process 回调函数* arg 回调函数参数* return Valr: 0 成功* -1 失败*/ int (* AddWorkUnlimit)(void * pthis, void * (* process)(void * arg), void * arg);/*** function: ThreadPoolAddWorkLimit* description: 向线程池投递任务,无空闲线程则阻塞* input param: pthis 线程池指针* process 回调函数* arg 回调函数参数* return Val: 0 成功* -1 失败*/ int (* AddWorkLimit)(void * pthis, void * (* process)(void * arg), void * arg);/*** function: ThreadPoolGetThreadMaxNum* description: 获取线程池可容纳的最大线程数* input param: pthis 线程池指针*/ int (* GetThreadMaxNum)(void * pthis);/*** function: ThreadPoolGetCurrentThreadNum* description: 获取线程池存放的线程数* input param: pthis 线程池指针* return Val: 线程池存放的线程数*/ int (* GetCurrentThreadNum)(void * pthis);/*** function: ThreadPoolGetCurrentTaskThreadNum* description: 获取当前正在执行任务和已经分配任务的线程数目和* input param: pthis 线程池指针* return Val: 当前正在执行任务和已经分配任务的线程数目和*/ int (* GetCurrentTaskThreadNum)(void * pthis);/*** function: ThreadPoolGetCurrentWaitTaskNum* description: 获取线程池等待队列任务数* input param: pthis 线程池指针* return Val: 等待队列任务数*/ int (* GetCurrentWaitTaskNum)(void * pthis);/*** function: ThreadPoolDestroy* description: 销毁线程池* input param: pthis 线程池指针* return Val: 0 成功* -1 失败*/ int (* Destroy)(void * pthis);
};
线程池的控制流程
线程池的控制流程可以分为三个步骤:
- 线程池创建。即创建
max_num
个线程ThreadPoolRoutine
,即空闲线程:
/*** function: ThreadPoolConstruct* description: 构建线程池* input param: max_num 线程池可容纳的最大线程数* free_num 线程池允许存在的最大空闲线程,超过则将线程释放回操作系统* return Val: 线程池指针 */
CThread_pool_t *
ThreadPoolConstruct(int max_num, int free_num)
{int i = 0;CThread_pool_t * pool = (CThread_pool_t *)malloc(sizeof(CThread_pool_t));if(NULL == pool)return NULL;memset(pool, 0, sizeof(CThread_pool_t));/*初始化互斥锁*/pthread_mutex_init(&(pool->queue_lock), NULL);/*初始化条件变量*/pthread_cond_init(&(pool->queue_ready), NULL);pool->queue_head = NULL;pool->max_thread_num = max_num; // 线程池可容纳的最大线程数pool->current_wait_queue_num = 0;pool->current_pthread_task_num = 0;pool->shutdown = 0;pool->current_pthread_num = 0;pool->free_pthread_num = free_num; // 线程池允许存在最大空闲线程pool->threadid = NULL;pool->threadid = (pthread_t *)malloc(max_num*sizeof(pthread_t));/*该函数指针赋值*/pool->AddWorkUnlimit = ThreadPoolAddWorkUnlimit;pool->AddWorkLimit = ThreadPoolAddWorkLimit;pool->Destroy = ThreadPoolDestroy;pool->GetThreadMaxNum = ThreadPoolGetThreadMaxNum;pool->GetCurrentThreadNum = ThreadPoolGetCurrentThreadNum;pool->GetCurrentTaskThreadNum = ThreadPoolGetCurrentTaskThreadNum;pool->GetCurrentWaitTaskNum = ThreadPoolGetCurrentWaitTaskNum;for(i=0; i<max_num; i++) {pool->current_pthread_num++; // 当前池中的线程数/*创建线程*/pthread_create(&(pool->threadid[i]), NULL, ThreadPoolRoutine, (void *)pool);usleep(1000); }return pool;
}
- 投递任务。即将任务生产者,将任务节点投入线程池中。实现如下:
/*** function: ThreadPoolAddWorkLimit* description: 向线程池投递任务,无空闲线程则阻塞* input param: pthis 线程池指针* process 回调函数* arg 回调函数参数* return Val: 0 成功* -1 失败*/
int
ThreadPoolAddWorkLimit(void * pthis, void * (* process)(void * arg), void * arg)
{ // int FreeThreadNum = 0;// int CurrentPthreadNum = 0;CThread_pool_t * pool = (CThread_pool_t *)pthis;/*为添加的任务队列节点分配内存*/worker_t * newworker = (worker_t *)malloc(sizeof(worker_t)); if(NULL == newworker) return -1;newworker->process = process; // 回调函数,在线程ThreadPoolRoutine()中执行newworker->arg = arg; // 回调函数参数newworker->next = NULL; pthread_mutex_lock(&(pool->queue_lock));/*插入新任务队列节点*/worker_t * member = pool->queue_head; // 指向任务队列链表整体if(member != NULL) {while(member->next != NULL) // 队列中有节点member = member->next; // member指针往后移动member->next = newworker; // 插入到队列链表尾部} else pool->queue_head = newworker; // 插入到队列链表头assert(pool->queue_head != NULL);pool->current_wait_queue_num++; // 等待队列加1/*空闲的线程= 当前线程池存放的线程 - 当前已经执行任务和已分配任务的线程数目和*/int FreeThreadNum = pool->current_pthread_num - pool->current_pthread_task_num;/*如果没有空闲线程且池中当前线程数不超过可容纳最大线程*/if((0 == FreeThreadNum) && (pool->current_pthread_num < pool->max_thread_num)) { //-> 条件为真进行新线程创建int CurrentPthreadNum = pool->current_pthread_num;/*新增线程*/pool->threadid = (pthread_t *)realloc(pool->threadid, (CurrentPthreadNum+1) * sizeof(pthread_t));pthread_create(&(pool->threadid[CurrentPthreadNum]),NULL, ThreadPoolRoutine, (void *)pool);/*当前线程池中线程总数加1*/ pool->current_pthread_num++;/*分配任务线程数加1*/pool->current_pthread_task_num++;pthread_mutex_unlock(&(pool->queue_lock));/*发送信号给一个处与条件阻塞等待状态的线程*/pthread_cond_signal(&(pool->queue_ready));return 0;}pool->current_pthread_task_num++;pthread_mutex_unlock(&(pool->queue_lock));/*发送信号给一个处与条件阻塞等待状态的线程*/pthread_cond_signal(&(pool->queue_ready));
// usleep(10); //看情况 return 0;
}
- 线程执行。即每一个线程的执行逻辑。实现如下:
/*** function: ThreadPoolRoutine* description: 线程池中执行的线程* input param: arg 线程池指针*/
void *
ThreadPoolRoutine(void * arg)
{CThread_pool_t * pool = (CThread_pool_t *)arg;while(1) {/*上锁,pthread_cond_wait()调用会解锁*/pthread_mutex_lock(&(pool->queue_lock));/*队列没有等待任务*/while((pool->current_wait_queue_num == 0) && (!pool->shutdown)) {/*条件锁阻塞等待条件信号*/pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));}if(pool->shutdown) {pthread_mutex_unlock(&(pool->queue_lock));pthread_exit(NULL); // 释放线程}assert(pool->current_wait_queue_num != 0);assert(pool->queue_head != NULL);pool->current_wait_queue_num--; // 等待任务减1,准备执行任务worker_t * worker = pool->queue_head; // 去等待队列任务节点头pool->queue_head = worker->next; // 链表后移 pthread_mutex_unlock(&(pool->queue_lock));(* (worker->process))(worker->arg); // 执行回调函数pthread_mutex_lock(&(pool->queue_lock));pool->current_pthread_task_num--; // 函数执行结束free(worker); // 释放任务结点worker = NULL;if((pool->current_pthread_num - pool->current_pthread_task_num) > pool->free_pthread_num) {pthread_mutex_unlock(&(pool->queue_lock));break; // 当线程池中空闲线程超过 free_pthread_num 则将线程释放回操作系统}pthread_mutex_unlock(&(pool->queue_lock)); }pool->current_pthread_num--; // 当前线程数减1pthread_exit(NULL); // 释放线程return (void *)NULL;
}
这个就是用来执行任务的线程,在初始化创建线程时所有线程都全部阻塞在pthread_cond_wait()处,此时的线程就为空闲线程,也就是线程被挂起,当收到信号并取得互斥锁时,表明任务投递过来
则获取等待队列里的任务结点并执行回调函数;函数执行结束后回去判断当前等待队列是否还有任务,有则接下去执行,否则重新阻塞回到空闲线程状态。
若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途
总结
实际上,我觉得在诊断项目中,线程池技术是非必要的。因此它不会涉及到大量的请求,以及每一个请求处理,一般都会比较耗时。
参考:https://www.cnblogs.com/zhaoosheLBJ/p/9337291.html
相关文章:

一文搞懂系列——Linux C线程池技术
背景 最近在走读诊断项目代码时,发现其用到了线程池技术,感觉耳目一新。以前基本只是听过线程池,但是并没有实际应用。对它有一丝的好奇,于是趁这个机会深入了解一下线程池的实现原理。 线程池的优点 线程池出现的背景…...

stable diffusion代码学习笔记
前言:本文没有太多公式推理,只有一些简单的公式,以及公式和代码的对应关系。本文仅做个人学习笔记,如有理解错误的地方,请指出。 本文包含stable diffusion入门文献和不同版本的代码。 文献资源 本文学习的代码&…...

腾讯云服务器怎么买?两种购买方式更省钱
腾讯云服务器购买流程很简单,有两种购买方式,直接在官方活动上购买比较划算,在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵,但是自定义购买云服务器CPU内存带宽配置选择范围广,活动上购买只能选择固定的活动…...
基于SpringBoot自定义控制是否需要开启定时功能
在基于SpringBoot的开发过程中,有时候会在应用中使用定时任务,然后服务器上启动定时任务,本地就不需要开启定时任务,使用一个参数进行控制,通过查资料得知非常简单。 参数配置 在application-dev.yml中加入如下配置 …...

“确定要在不复制其属性的情况下复制此文件?”解决方案(将U盘格式由FAT格式转换为NTFS格式)
文章目录 1.问题描述2.问题分析3.问题解决3.1 方法一3.2 方法二3.3 方法三 1.问题描述 从电脑上复制文件到U盘里会出现“确定要在不复制其属性的情况下复制此文件?”提示。 2.问题分析 如果这个文件在NTFS分区上,且存在特殊的安全属性。那么把它从NT…...

视频监控系统EasyCVR如何通过调用API接口查询和下载设备录像?
智慧安防平台EasyCVR是基于各种IP流媒体协议传输的视频汇聚和融合管理平台。视频流媒体服务器EasyCVR采用了开放式的网络结构,支持高清视频的接入和传输、分发,平台提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联…...

15.鸿蒙HarmonyOS App(JAVA)进度条与圆形进度条
15.鸿蒙HarmonyOS App(JAVA)进度条与圆形进度条 progressBar2.setIndeterminate(true);//设置无限模式,运行查看动态效果 //创建并设置无限模式元素 ShapeElement element new ShapeElement(); element.setBounds(0,0,50,50); element.setRgbColor(new RgbColor(255,0,0)); …...
【FastAPI】路径参数
路径参数 from fastapi import FastAPIapp FastAPI()app.get("/items/{item_id}") async def read_item(item_id):return {"item_id": item_id}其中{item_id}就为路径参数 运行以上程序当访问 :http://127.0.0.1:8000/items/fastapi时候 将会…...

【docker笔记】DockerFile
DockerFile Docker镜像结构的分层 镜像不是一个单一的文件,而是有多层构成。 容器其实是在镜像的最上面加了一层读写层,在运行容器里做的任何文件改动,都会写到这个读写层。 如果删除了容器,也就是删除了其最上面的读写层&…...

React项目搭建流程
第一步 利用脚手架创建ts类型的react项目: 执行如下的命令:create-react-app myDemo --template typescript ; 第二步 清理项目目录结构: src/ index.tsx, app.txs, react-app-env.d.ts public/index.ht…...

QT DAY1作业
1.QQ登录界面 头文件代码 #ifndef MYWIDGET_H #define MYWIDGET_H#include <QWidget> #include <QIcon> #include <QLabel> #include <QPushButton> #include <QMovie> #include <QLineEdit>class MyWidget : public QWidget {Q_OBJECTpu…...

Java后端开发——Mybatis实验
文章目录 Java后端开发——Mybatis实验一、MyBatis入门程序1.创建工程2.引入相关依赖3.数据库准备4.编写数据库连接信息配置文件5.创建POJO实体6.编写核心配置文件和映射文件 二、MyBatis案例:员工管理系统1.在mybatis数据库中创建employee表2.创建持久化类Employee…...

【UE Niagara 网格体粒子系列】02-自定义网格
目录 步骤 一、创建自定义网格体 二、创建Niagara系统 步骤 一、创建自定义网格体 1. 打开Blender,按下ShiftA来创建一个平面 将该平面旋转90 导出为fbx 设置导出选定的物体,这里命名为“SM_PlaneFaceCamera.fbx” 按H隐藏刚才创建的平面&#x…...
k8s 检测node节点内存使用率平衡调度脚本 —— 筑梦之路
直接上脚本: #! /bin/bash#对实际使用内存大于85%的机器停止调度,对实际使用内存小于70%的 关闭调度# 获取实际内存小于或等于70%的机器 memory_lt_70kubectl top nodes |awk NR>1{if($50<70) print $1} # 获取实际内存大于或等于85%的机器 memor…...

React Native集成到现有原生应用
本篇文章以MacOS环境开发iOS平台为例,记录一下在原生APP基础上集成React Native React Native中文网 详细介绍了搭建环境和集成RN的步骤。 环境搭建 必须安装的依赖有:Node、Watchman、Xcode 和 CocoaPods。 安装Homebrew Homebrew是一款Mac OS平台下…...

完全卸载grafana
先停掉grafana sudo systemctl stop grafana-server 查看要卸载的包的名字 yum list installed yum remove grafana-enterprise.x86_64 成功 删除grafana的数据目录 sudo rm -rf /etc/grafana/sudo rm -rf /usr/share/grafana/sudo rm -rf /var/lib/grafana/...

Vue2.组件通信
样式冲突 写在组件中的样式默认会全局生效。容易造成多个组件之间的样式冲突问题。 可以给组件加上scoped属性,让样式只作用于当前组件。 原理: 给当前组件模板的所有元素,加上一个自定义属性data-v-hash值,用以区分不同的组件。…...

CAS的超~详细介绍
什么是CAS CAS全称Compare and swap,是一种比较特殊的CPU指令. 字面意思:"比较并交换", 一个CAS涉及到以下操作: 我们假设内存中的原数据为V,旧的预期值A,需要修改的新值B. 1.比较A和V是否相等(比较) 2.如果相等,将B写入V.(交换) 3.返回操作是否成功. 伪代码 下面…...

Scott用户数据表的分析
Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 如果想要知道某个用户所有的数据表: select * from tab; 此时结果中一共返回了四张数据表,分别为部门表(dept) ,员工表(emp&a…...

网络基础学习(3):交换机
1.交换机结构 (1)网线接口和后面的电路部分加在一起称为一个端口,也就是说交换机的一个端口就相当于计算机上的一块网卡。 如果在计算机上安装多个网卡,并让网卡接收所有网络包,再安装具备交换机功能的软件࿰…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
SpringAI实战:ChatModel智能对话全解
一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM࿰…...