一文搞懂系列——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);    
};

线程池的控制流程

线程池的控制流程可以分为三个步骤:

  1. 线程池创建。即创建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;
}
  1. 投递任务。即将任务生产者,将任务节点投入线程池中。实现如下:
/***  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;
}
  1. 线程执行。即每一个线程的执行逻辑。实现如下:
/***  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

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

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

相关文章

RocketMQ源码阅读-Message拉取与消费-Broker篇

RocketMQ源码阅读-Message拉取与消费-Broker篇 1. ConsumeQueue是什么2. Message重放2.1 从MappedFile文件读取Message到ConsumeQueue2.2 ConsumeQueue持久化 3. Broker提供的拉取接口3.1 请求Header3.2 拉取消息接口3.3 拉取失败处理 4. Broker提供的更新消费进度接口5. Broke…

C++之类的静态成员

C静态成员是指在类中使用static关键字声明的成员变量或成员函数。静态成员属于类本身&#xff0c;而不是类的对象。它们在所有对象之间共享&#xff0c;只有一份内存空间。静态成员在类外初始化&#xff0c;且只能访问一次。 关键字 static 意味着类中只有一个该成员的实例。静…

Redis面试题16

Redis 的主从复制是什么&#xff1f;它有什么作用&#xff1f; 答&#xff1a;主从复制是指 Redis 中的主节点将自己的数据复制给从节点的过程。主从复制为 Redis 提供了数据的冗余备份、读写分离和故障恢复等功能。 主从复制的过程如下&#xff1a; 主节点将自己的数据变更操…

短视频IP运营流程架构SOP模板PPT

【干货资料持续更新&#xff0c;以防走丢】 短视频IP运营流程架构SOP模板PPT 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 抖音运营资料合集&#xff08;完整资料包含以下内容&#xff09; 目录 抖音15秒短视频剧本创作公式 在抖音这个短视频平台上&#…

【FastAPI】路径参数

路径参数 from fastapi import FastAPIapp FastAPI()app.get("/items/{item_id}") async def read_item(item_id):return {"item_id": item_id}其中{item_id}就为路径参数 运行以上程序当访问 &#xff1a;http://127.0.0.1:8000/items/fastapi时候 将会…

SpringBoot集成RabbitMq,RabbitMq消费与生产,消费失败重发机制,发送签收确认机制

RabbitMq消费与生产&#xff0c;消费失败重发机制&#xff0c;发送确认机制&#xff0c;消息发送结果回执 1. RabbitMq集成spring bootRabbitMq集成依赖RabbitMq配置RabbitMq生产者&#xff0c;队列&#xff0c;交换通道配置&#xff0c;消费者示例 2. RabbitMq消息确认机制消息…

力扣labuladong一刷day59天动态规划

力扣labuladong一刷day59天动态规划 文章目录 力扣labuladong一刷day59天动态规划一、509. 斐波那契数二、322. 零钱兑换 一、509. 斐波那契数 题目链接&#xff1a;https://leetcode.cn/problems/fibonacci-number/description/ 思路&#xff1a;这是非常典型的一道题&#x…

【例7.5】 取余运算(mod) 快速幂

1326&#xff1a;【例7.5】 取余运算&#xff08;mod&#xff09; 时间限制: 1000 ms 内存限制: 65536 KB 【题目描述】 输入b&#xff0c;p&#xff0c;k的值&#xff0c;求bpmodk 的值。其中b&#xff0c;p&#xff0c;kk为长整型数。 【输入】 输入b&#xff0c;p&#xf…

Scott用户数据表的分析

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

【LV12 DAY20 RTC实验】

编程实现通过LED状态显示当前电压范围&#xff0c;并打印产生低压警报时的时间 注&#xff1a; 电压在1501mv~1800mv时&#xff0c;LED2、LED3、LED4、LED5点亮 电压在1001mv~1500mv时&#xff0c;LED2、LED3、LED4点亮 电压在501mv~1000mv时&#xff0c;LED2、LED3点亮 电压在…

C++算法学习心得六.回溯算法(1)

1.回溯算法理论基础 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。回溯的本质是穷举,穷举所有可能&#xff0c;然后选出我们想要的答案 回溯法解决的问题 组合问题&#xff1a;N个数里面按一定规则找出…

HTML--CSS--浮动布局及定位布局

正常文档布局 块元素独占一行 行内元素在有多个的时候&#xff0c;就是从左到右排在一行 块元素包括&#xff1a;div,p,hr 行内元素&#xff1a;span,i,img 浮动布局 float 属性&#xff1a; left 向左 right 向右 作用我目前看起来就是浮动元素的宽度是由内容决定的&#x…

Doris学习笔记-Java自定义UDAF

项目最近需要做一些数据统计方面的东西,发现数据字段都是很长一串数字的字符串,Doris自带的函数无法对其进行相应的运算操作,需要扩展实现相关的操作运算。 主要参考官方的文档资料完成相关的自定义扩展。需要注意的是在使用Java代码编写UDAF时,有一些必须实现的函数(标记…

网络安全服务

有效防御Cc、API接口、http、tcp、WEB应用扫描/爬虫、SYN、WAF、DDOS、UDP、入侵、渗透、SQL注入、XXS跨站脚本攻击、远程恶意代码执行、session ion fixation、Webshell攻击、恶意请求&#xff0c;恶意扫描、暴击破解、CSRF等各种攻击。不用迁移数据&#xff0c;服务器用哪里…

HDFS和MapReduce综合实训

文章目录 第1关&#xff1a;WordCount词频统计第2关&#xff1a;HDFS文件读写第3关&#xff1a;倒排索引第4关&#xff1a; 网页排序——PageRank算法 第1关&#xff1a;WordCount词频统计 测试说明 以下是测试样例&#xff1a; 测试输入样例数据集&#xff1a;文本文档test1…

Java实战之每日海报

前言 使用java生成每日海报。 项目起因是巧合下遇到了一篇很棒的文档&#xff0c;说的是用程序来实现每日生成一个海报。如果之后加上自动发布的功能&#xff0c;简直就是太棒了啊&#xff01; 样例图如下&#xff1a; 每日海报 思路 访问某词站的API获取网络图片&#…

GBASE regexp_replace函数 与 db2 translate函数比较

db2 translate函数 以下内容参考自文档&#xff1a; translate 函数 官方示例: 示例1&#xff1a; fn:translate(Test literal,el,om) -- RETURN RESULT: Tost mitoram上述式子的意思为&#xff1a; 对于字符串&#xff1a;‘Test literal’&#xff0c;使用o 替代 e,使用 m…

在线客服系统如何与社交媒体集成?

在线客服系统与社交媒体的集成可以实现以下功能&#xff1a; 直接接入社交媒体平台&#xff1a;通过API集成&#xff0c;客服系统可以与主流社交媒体平台&#xff08;如微信、微博、Facebook等&#xff09;直接对接&#xff0c;客户可以直接在社交媒体上发起咨询或投诉&#x…

Java持久层框架之争:选择最佳方案来提升你的开发效率!

1、前言 在现代软件开发领域&#xff0c;选择适合的持久层框架是至关重要的一步。持久层框架可以帮助我们管理数据访问、数据库连接、事务处理等复杂的数据库操作&#xff0c;从而提升开发效率和代码质量。 然而&#xff0c;在众多的Java持久层框架中&#xff0c;选择最佳方案并…

ActiveMQ:专注消息传递,助您构建高效稳定的系统

在数字化世界的今天&#xff0c;应用程序和系统之间的通信变得日益重要&#xff0c;为了确保数据能够在不同的服务和组件之间高效、可靠地传输&#xff0c;消息队列技术应运而生。 Apache ActiveMQ 作为一种流行的开源消息队列技术&#xff0c;为企业级应用提供了强大的支持&am…