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

线程池-手写线程池Linux C简单版本(生产者-消费者模型)

目录

  • 简介
  • 手写线程池
    • 线程池结构体分析
      • task_t
      • task_queue_t
      • thread_pool_t
    • 线程池函数分析
      • thread_pool_create
      • thread_pool_post
      • thread_worker
      • thread_pool_destroy
      • wait_all_done
      • thread_pool_free
    • 主函数调用
  • 运行结果

简介

本线程池采用C语言实现

线程池的场景:

当某些任务特别耗时(例如大量的IO读写操作),严重影响线程其他的任务的执行,可以使用线程池

线程池的一般特点:

线程池通常是一个生产者-消费者模型
生产者线程用于发布任务,任务通常保存在任务队列中
线程池作为消费者,用于取出任务,执行任务

线程池中线程数量的选择:

有一个经验公式: 线程数量 =(io等待时间+cpu运算时间)*核心数/cpu运算时间

因此可以根据经验公式得出下面两种场景的线程数量:

  • cpu密集任务:线程数量=核心数(即上面的公式假设cpu运算时间>>io等待时间)
  • io密集任务:线程数量=2*n+2

手写线程池

线程池代码结构:

  • thread_pool_create:创建线程池所需要的资源,包含不限于任务队列,子线程的创建。
  • thread_pool_post:用于任务的发布,将执行任务存在任务队列中。
  • thread_pool_destroy:用于线程池的退出,以及资源的销毁。
  • wait_all_done:join线程池所有子线程,等待回收子线程。
  • thread_worker:用于任务执行。

主要的核心点集中在thread_pool_post和thread_worker两个函数中,这两个函数也构成了生产者-消费者模型。本文采用队列+互斥锁+条件变量实现。

线程池结构体分析

由于C语言不像C++可以用类封装函数,因此线程池会使用结构体来封装一些变量或者函数指针。

task_t

封装任务的入口指针以及参数。

typedef struct task_t {handler_pt func;void * arg;
} task_t;

task_queue_t

封装任务队列,为了不频繁移动队列中数据,此处采用头尾索引来标记任务。

typedef struct task_queue_t {uint32_t head;uint32_t tail;uint32_t count;task_t *queue;
} task_queue_t;

thread_pool_t

包含互斥锁,条件变量,任务队列等信息

struct thread_pool_t {pthread_mutex_t mutex;pthread_cond_t condition; //条件变量pthread_t *threads; //线程task_queue_t task_queue; //任务队列int closed; //是否关闭线程池执行的标志,为1表示关闭int started; // 当前正在运行的线程数int thrd_count; //线程数int queue_size; //任务队列大小
};

其中closed:表示是否关闭线程池执行的标志,为1表示关闭。在线程的运行函数中,用来判断是否继续循环等待执行任务队列中的任务。
started:表示当前正在运行的线程数。在thread_pool_destroy函数中销毁线程池时,需要等待所有线程停止才行,即started == 0

线程池函数分析

thread_pool_create

创建线程池,初始化一些线程池属性
通过循环pthread_create函数创建子线程。

thread_pool_t *thread_pool_create(int thrd_count, int queue_size) {thread_pool_t *pool;if (thrd_count <= 0 || queue_size <= 0) {return NULL;}pool = (thread_pool_t*) malloc(sizeof(*pool));if (pool == NULL) {return NULL;}pool->thrd_count = 0;pool->queue_size = queue_size;pool->task_queue.head = 0;pool->task_queue.tail = 0;pool->task_queue.count = 0;pool->started = pool->closed = 0;pool->task_queue.queue = (task_t*)malloc(sizeof(task_t)*queue_size);if (pool->task_queue.queue == NULL) {// TODO: free poolreturn NULL;}pool->threads = (pthread_t*) malloc(sizeof(pthread_t) * thrd_count);if (pool->threads == NULL) {// TODO: free poolreturn NULL;}int i = 0;for (; i < thrd_count; i++) {if (pthread_create(&(pool ->threads[i]), NULL, thread_worker, (void*)pool) != 0) {// TODO: free poolreturn NULL;}pool->thrd_count++;pool->started++;}return pool;
}

thread_pool_post

作为生产者,往任务队列里面添加任务
通过pthread_cond_signal通知子唤醒子线程的pthread_cond_wait

int thread_pool_post(thread_pool_t *pool, handler_pt func, void *arg) {if (pool == NULL || func == NULL) {return -1;}task_queue_t *task_queue = &(pool->task_queue);
//此处用自旋锁会更节省消耗,因为锁里面的逻辑比较简单if (pthread_mutex_lock(&(pool->mutex)) != 0) {return -2;}if (pool->closed) {pthread_mutex_unlock(&(pool->mutex));return -3;}if (task_queue->count == pool->queue_size) {pthread_mutex_unlock(&(pool->mutex));return -4;}
//避免queue数据的变化,采用头尾索引来标识task_queue->queue[task_queue->tail].func = func;task_queue->queue[task_queue->tail].arg = arg;task_queue->tail = (task_queue->tail + 1) % pool->queue_size;task_queue->count++;
//唤醒一个休眠的线程if (pthread_cond_signal(&(pool->condition)) != 0) {pthread_mutex_unlock(&(pool->mutex));return -5;}pthread_mutex_unlock(&(pool->mutex));return 0;
}

thread_worker

pthread_cond_wait等待任务的唤醒
作为消费者, (*(task.func))(task.arg);执行任务

static void *thread_worker(void *thrd_pool) {thread_pool_t *pool = (thread_pool_t*)thrd_pool;task_queue_t *que;task_t task;for (;;) {pthread_mutex_lock(&(pool->mutex));que = &pool->task_queue;while (que->count == 0 && pool->closed == 0) {// 阻塞在 condition,等待任务队列添加任务pthread_cond_wait(&(pool->condition), &(pool->mutex));}if (pool->closed == 1 && que->count == 0) break;//没有任务,并且关闭标志打开,即跳出循环task = que->queue[que->head];que->head = (que->head + 1) % pool->queue_size;que->count--;pthread_mutex_unlock(&(pool->mutex));(*(task.func))(task.arg);//执行对应任务函数}pool->started--;//跳出循环之后,运行线程数需要减1pthread_mutex_unlock(&(pool->mutex));pthread_exit(NULL);return NULL;
}

thread_pool_destroy

销毁释放线程池,置 pool->closed = 1;
通过pthread_cond_broadcast唤醒线程池所有线程,这个和thread_pool_post里的pthread_cond_signal一样,并且broadcast会通知到所有的线程

int thread_pool_destroy(thread_pool_t *pool) {if (pool == NULL) {return -1;}if (pthread_mutex_lock(&(pool->mutex)) != 0) {return -2;}if (pool->closed) {thread_pool_free(pool);return -3;}pool->closed = 1;
//广播形式,通知所有阻塞在condition的线程接触阻塞if (pthread_cond_broadcast(&(pool->condition)) != 0 || pthread_mutex_unlock(&(pool->mutex)) != 0) {thread_pool_free(pool);return -4;}wait_all_done(pool);thread_pool_free(pool);return 0;
}

wait_all_done

将所有线程通过pthread_join回收,所有子线程任务执行完毕,回收线程

int wait_all_done(thread_pool_t *pool) {printf("wait_all_done start!pool->thrd_count:%d\n", pool->thrd_count);int i, ret=0;for (i=0; i < pool->thrd_count; i++) {printf("wait_all_done doing! i:%d\n", i);if (pthread_join(pool->threads[i], NULL) != 0) {ret=1;}}printf("wait_all_done end!\n");return ret;
}

thread_pool_free

释放线程池空间

static void thread_pool_free(thread_pool_t *pool) {if (pool == NULL || pool->started > 0) {return;}if (pool->threads) {free(pool->threads);pool->threads = NULL;pthread_mutex_lock(&(pool->mutex));pthread_mutex_destroy(&pool->mutex);pthread_cond_destroy(&pool->condition);}if (pool->task_queue.queue) {free(pool->task_queue.queue);pool->task_queue.queue = NULL;}free(pool);
}

主函数调用

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>#include "thrd_pool.h"int nums = 0;
int done = 0;
int task_num = 100;pthread_mutex_t lock;void do_task(void *arg) {usleep(10000);pthread_mutex_lock(&lock);done++;printf("doing %d task\n", done);pthread_mutex_unlock(&lock);
}int main(int argc, char **argv) {int threads = 8;int queue_size = 256;if (argc == 2) {threads = atoi(argv[1]);if (threads <= 0) {printf("threads number error: %d\n", threads);return 1;}} else if (argc > 2) {threads = atoi(argv[1]);queue_size = atoi(argv[1]);if (threads <= 0 || queue_size <= 0) {printf("threads number or queue size error: %d,%d\n", threads, queue_size);return 1;}}thread_pool_t *pool = thread_pool_create(threads, queue_size);if (pool == NULL) {printf("thread pool create error!\n");return 1;}while (thread_pool_post(pool, &do_task, NULL) == 0) {pthread_mutex_lock(&lock);nums++;pthread_mutex_unlock(&lock);if (nums > task_num) break;}printf("add %d tasks\n", nums);usleep(1000000);//延时等待所有的作业完成printf("did %d tasks\n", done);thread_pool_destroy(pool);return 0;
}

运行结果

使用指令编译文件:

gcc main.c thrd_pool.c -o main -lpthread

运行执行文件得到运行结果
在这里插入图片描述在这里插入图片描述

完整代码下载线程池Linux C语言简单版本

相关文章:

线程池-手写线程池Linux C简单版本(生产者-消费者模型)

目录 简介手写线程池线程池结构体分析task_ttask_queue_tthread_pool_t 线程池函数分析thread_pool_createthread_pool_postthread_workerthread_pool_destroywait_all_donethread_pool_free 主函数调用 运行结果 简介 本线程池采用C语言实现 线程池的场景&#xff1a; 当某些…...

05-向量的意义_n维欧式空间

线性代数 什么是向量&#xff1f;究竟为什么引入向量&#xff1f; 为什么线性代数这么重要&#xff1f;从研究一个数拓展到研究一组数 一组数的基本表示方法——向量&#xff08;Vector&#xff09; 向量是线性代数研究的基本元素 e.g. 一个数&#xff1a; 666&#xff0c;…...

交通运输安全大数据分析解决方案

当前运输市场竞争激烈&#xff0c;道路运输企业受传统经营观念影响&#xff0c;企业管理者安全意识淡薄&#xff0c;从业人员规范化、流程化的管理水平较低&#xff0c;导致制度规范在落实过程中未能有效监督与管理&#xff0c;执行过程中出现较严重的偏差&#xff0c;其营运车…...

vimrc 配置 (持续跟新中)

vimrc 配置 #显示行号 set nu #自动换行 set autoindent #设置tab键 宽度为四个空格 set tabstop4 set shiftwidth4 set expandtab更多文章&#xff0c;详见我的博客网站...

【集成学习介绍】

1. 引言 在机器学习领域&#xff0c;集成学习&#xff08;Ensemble Learning&#xff09;是一种强大的技术&#xff0c;通过将多个弱学习器组合成一个更强大的集成模型&#xff0c;来提升模型的鲁棒性和性能。 2. 集成学习的原理 集成学习的核心思想是“三个臭皮匠&#xff…...

动画制作选择Blender还是Maya

Blender和Maya是两种最广泛使用的 3D 建模和动画应用程序。许多经验丰富的用户表示&#xff0c;Blender 在雕刻工具方面远远领先于 Maya&#xff0c;并且在 3D 建模方面达到了相同的质量水平。对于刚接触动画行业的人来说&#xff0c;您可能会问“我应该使用 Blender 还是 Maya…...

215. 数组中的第K个最大元素

题目链接&#xff1a;力扣 解题思路&#xff1a; 方法一&#xff1a;基于快速排序 因为题目中只需要找到第k大的元素&#xff0c;而快速排序中&#xff0c;每一趟排序都可以确定一个最终元素的位置。 当使用快速排序对数组进行降序排序时&#xff0c;那么如果有一趟排序过程…...

NLP From Scratch: 生成名称与字符级RNN

NLP From Scratch: 生成名称与字符级RNN 这是我们关于“NLP From Scratch”的三个教程中的第二个。 在<cite>第一个教程< / intermediate / char_rnn_classification_tutorial ></cite> 中&#xff0c;我们使用了 RNN 将名称分类为来源语言。 这次&#xff…...

Spring MVC程序开发

目录 1.什么是Spring MVC? 1.1MVC定义 1.2MVC和Spring MVC的关系 2.为什么要学习Spring MVC? 3.怎么学Spring MVC? 3.1Spring MVC的创建和连接 3.1.1创建Spring MVC项目 3.1.2RequestMapping 注解介绍 3.1.3 RequestMapping 是 post 还是 get 请求&#xff1f; ​…...

医疗知识图谱问答——文本分类解析

前言 Neo4j的数据库构建完成后&#xff0c;现在就是要实现医疗知识的解答功能了。因为是初版&#xff0c;这里的问题解答不会涉及深度学习&#xff0c;目前只是一个条件查询的过程。而这个过程包括对问题的关键词拆解分类&#xff0c;然后提取词语和类型去图数据库查询&#xf…...

JS关于多张图片上传显示报错不影响后面图片上传方法

关于多张图片上传或者下载显示报错后会程序会终止执行&#xff0c;从而影响后面图片上传。 解决方法&#xff1a; /*能正常访问的图片*/ const url https://2vimg.hitv.com/100/2308/0109/5359/dqKIZ7d4cnHL/81Vu0c.jpg?x-oss-processimage/format,webp; /*不能正常下载的图…...

MySQL踩坑之sql_mode的用法

目录 定义 报错重现 ​编辑 原因分析 sql_mode值说明 查看当前sql_mode 设置sql_mode 定义 什么是sql_mode?玩了这么久的MySQL语句࿰...

消息队列总结(4)- RabbitMQ Kafka RocketMQ高性能方案

1.RabbitMQ的高性能解决方案 1.1 发布确认机制 RabbitMQ提供了3种生产者发布确认的模式&#xff1a; 简单模式&#xff08;Simple Mode&#xff09;&#xff1a;生产者发送消息后&#xff0c;等待服务器确认消息已经被接收。这种模式下&#xff0c;生产者发送消息后会阻塞&am…...

websocket服务端大报文发送连接自动断开分析

概述 当前springboot版本&#xff1a;2.7.4 使用依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency>现象概述&#xff1a; 客户端和服务端已经有心跳…...

想写几个上位机,是选择学c#还是 c++ qt呢?

C#基本也就上位机开发开发&#xff0c;另外做做日常用的小工具很方便。 结合PLC&#xff0c;以太网做上位机&#xff0c;这个基本上控制这块都比较有需求。 另外我们用C#也做一些工具的二次开发&#xff0c;感觉还行。 C用qt框架其实学习起来可能稍微复杂些&#xff0c;但是…...

JavaScript 简单实现观察者模式和发布-订阅模式

JavaScript 简单实现观察者模式和发布-订阅模式 1. 观察者模式1.1 什么是观察者模式1.2 代码实现 2. 发布-订阅模式2.1 什么是发布-订阅模式2.2 代码实现2.2.1 基础版2.2.2 取消订阅2.2.3 订阅一次 1. 观察者模式 1.1 什么是观察者模式 概念&#xff1a;观察者模式定义对象间…...

java集成短信服务 测试版 qq邮箱简单思路

java集成短信服务 注册一个帐号 使用的是容联云&#xff0c;百度搜一下官网 用手机注册一个帐号就行&#xff0c;免费体验不需要认证 注册后会有八块钱送&#xff0c;可以使用免费的给自己设置三个固定手机号发送短信&#xff0c;不需要认证。 此页面的 三个信息需要在代码中…...

#P0994. [NOIP2004普及组] 花生采摘

题目描述 鲁宾逊先生有一只宠物猴&#xff0c;名叫多多。这天&#xff0c;他们两个正沿着乡间小路散步&#xff0c;突然发现路边的告示牌上贴着一张小小的纸条&#xff1a;“欢迎免费品尝我种的花生&#xff01;――熊字”。 鲁宾逊先生和多多都很开心&#xff0c;因为花生正…...

Elasticsearch和Kibana的安装及验证

金翅大鹏盖世英&#xff0c;展翅金鹏盖世雄。 穿云燕子锡今鸽&#xff0c;踏雪无痕花云平。 ---------------- 2023.7.31.101 ----------------- 本文密钥&#xff1a;365 Elasticsearch 是一个分布式的 RESTful 风格的搜索和数据分析引擎&#xff0c;常用来进行全文检索、…...

细讲TCP三次握手四次挥手(一)

计算机网络体系结构 在计算机网络的基本概念中&#xff0c;分层次的体系结构是最基本的。计算机网络体系结构的抽象概念较多&#xff0c;在学习时要多思考。这些概念对后面的学习很有帮助。 网络协议是什么&#xff1f; 在计算机网络要做到有条不紊地交换数据&#xff0c;就必…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

起重机起升机构的安全装置有哪些?

起重机起升机构的安全装置是保障吊装作业安全的关键部件&#xff0c;主要用于防止超载、失控、断绳等危险情况。以下是常见的安全装置及其功能和原理&#xff1a; 一、超载保护装置&#xff08;核心安全装置&#xff09; 1. 起重量限制器 功能&#xff1a;实时监测起升载荷&a…...

欢乐熊大话蓝牙知识17:多连接 BLE 怎么设计服务不会乱?分层思维来救场!

多连接 BLE 怎么设计服务不会乱&#xff1f;分层思维来救场&#xff01; 作者按&#xff1a; 你是不是也遇到过 BLE 多连接时&#xff0c;调试现场像网吧“掉线风暴”&#xff1f; 温度传感器连上了&#xff0c;心率带丢了&#xff1b;一边 OTA 更新&#xff0c;一边通知卡壳。…...