C 语言编程 — 线程池设计与实现

目录

文章目录

  • 目录
  • 线程池(Thread Pool)
  • tiny-threadpool
    • 数据结构设计
      • Task / Job
      • Task / Job Queue
      • Worker / Thread
      • Thread Pool Manager
    • Public APIs
    • Private Functions
    • 运行示例

线程池(Thread Pool)

线程池(Thread Pool)是一种用于管理多线程环境的技术。Thread Pool 会在 User Process 中预先创建一组 User Threads,并在需要时重复使用它们,而不是频繁的创建新线程。

线程池可以有效提高程序性能和可靠性:

  • 避免频繁创建、销毁线程,降低了系统开销;
  • 限制线程数量,防止过度占用系统资源;
  • 提高了程序的响应速度和吞吐量;
  • 管理线程的生命周期,避免了线程泄漏的风险;
  • 可实现任务优先级。

tiny-threadpool

  • Github:https://github.com/JmilkFan/tiny-threadpool

tiny-threadpool fork 自 C-Thread-Pool,是一个开源的轻量级线程池,实现了以下功能。

  • 符合 ANCI C 和 POSIX 风格;
  • 支持 Thread 的 Pause(暂停)、Resume(恢复)、Wait(等待);
  • 提供简洁的 APIs。

数据结构设计

在这里插入图片描述

Task / Job

Task / Job 本质是由 Producer 发出的任务请求,通常数量庞大,需要高并发的进行处理。

/**
 * Job 应该包含以下 3 个成员:
 *  1. 线程入口函数;
 * 	2. 线程入口函数的参数;
 * 	3. 指向下一个 Job 的指针。
 */
typedef struct job{
	struct job*  prev;  // 指向下一个 Job,添加 New Job 入队尾(Rear)时,上一次的 Rear Job,应该要指向下一个 New Job,然后 New Job 成为新的 Near Job。
	void   (*function)(void* arg);  // 线程入口函数的类型声明
	void*  arg;                     // 线程入口函数的参数的类型声明
} job;

Task / Job Queue

Queue 用于缓存 Jobs,并且是 FIFO 的。在某些场景中,可能会要求 Queue 的长度是可调整的,也可能会要求有多条不同优先级的 Queues。

/**
 * Job Queue 应该包含以下 5 个成员:
 *  1. 队头
 * 	2. 队尾
 *  3. 队长
 *  4. 互斥锁,保证高并发 Jobs 入队/出队是 FIFO 的。
 * 	5. 队列状态:1)空队列;2)非空队列;
 */
typedef struct jobqueue{
	job  *front;              // 指向队头
	job  *rear;               // 指向队尾
	int   len;                // 队列长度
	pthread_mutex_t rwmutex;  // 任务队列的锁
	bsem *has_jobs;           // 指向一个二元信号量,用于表示 Queue 是否为空。
} jobqueue;


/**
 * 二元信号量,用于标识 Job Queue 是否为空。
 * 	同样需要使用互斥锁和条件变量来保证高并发处理时二元值的安全性。
 */
typedef struct bsem {
	pthread_mutex_t mutex;  // 信号量的锁
	pthread_cond_t   cond;  // 信号量的条件
	int v;                  // 信号量的互斥值
							//   0:空队列;
							//   1:非空队列。
} bsem;

Worker / Thread

Worker / Thread 是 Thread Pool 分配出来真正处理 Job 的执行单元,它们是线程入口函数的 Caller(调用者),所以也称为 Consumer。

/**
 * Thread 应该包含以下 3 个成员:
 *  1. 友好的 ID,便于调试。区别于 Kernel 分配的 TID。
 * 	2. 指向 pthread 实体的指针
 *  3. 指向 Thread Pool 的指针,以此来获得/释放线程池的锁和条件变量
 */
typedef struct thread{
	int       id;              // 友好 ID
	pthread_t pthread;         // 指向一个 pThread 实体
	struct thpool_* thpool_p;  // 指向线程池
} thread;

Thread Pool Manager

Thread Pool Manager 用于管理 Thread Pool 资源:例如:创建/销毁多线程、维护任务队列、线程池状态、互斥锁和条件变量等。

当有 Job 需要处理时,Manager 就从 Pool 中获取一个可用的 Thread 来处理。当 Job 完成后,Manager 回收 Thread,而不是销毁它。

/**
 * Thread Pool Manager 应该包含以下 5 个成员:
 *  1. 多线程列表
 *  2. 活跃线程数量
 * 	3. 工作线程数量(可用线程数 = 活跃线程数量 - 工作线程数量)
 *  4. 任务队列
 *  5. 互斥锁
 *  6. 条件变量
 */
typedef struct thpool_{
	thread**   threads;                // 指向线程指针数组
	volatile int num_threads_alive;    // 当前活跃的线程数量
	volatile int num_threads_working;  // 当前工作中的线程数量
	jobqueue  jobqueue;                // 线程池关联的任务队列
	pthread_mutex_t  thcount_lock;     // 线程池的锁
	pthread_cond_t  threads_all_idle;  // 线程池的条件变量
} thpool_;

Public APIs

typedef struct thpool_* threadpool;

// 创建一个包含有指定数量线程的线程池
threadpool thpool_init(int num_threads);

// 添加 task 到 task queue 中。
int thpool_add_work(threadpool, void (*function_p)(void*), void* arg_p);

// 等待所有 tasks 执行完。
void thpool_wait(threadpool);

// 暂停所有 tasks,使它们进入 sleep 状态。通过信号机制来实现。
void thpool_pause(threadpool);

// 恢复所有 tasks 继续执行。
void thpool_resume(threadpool);

// 销毁所有 tasks,如果有 task 正在执行,则先等待 task 执行完。
void thpool_destroy(threadpool);

// 获取当前正在工作中的线程数量。
int thpool_num_threads_working(threadpool);

Private Functions

// 构造 struct thread,并调用 pthread_create() 创建线程
static int thread_init (thpool_* thpool_p, struct thread** thread_p, int id) 

// 当线程被暂停时会在这里休眠
static void thread_hold(int sig_id) 

// 线程在此函数中执行任务
static void* thread_do(struct thread* thread_p) 

// 销毁 struct thread
static void thread_destroy (thread* thread_p) 

// 任务队列相关的操作集合
static int jobqueue_init(jobqueue* jobqueue_p) 
static void jobqueue_clear(jobqueue* jobqueue_p) 
static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob) 
static struct job* jobqueue_pull(jobqueue* jobqueue_p) 
static void jobqueue_destroy(jobqueue* jobqueue_p) 

// 信号量相关的操作集合
static void bsem_init(bsem *bsem_p, int value) 
static void bsem_reset(bsem *bsem_p) 
static void bsem_post(bsem *bsem_p) 
static void bsem_post_all(bsem *bsem_p) 
static void bsem_wait(bsem* bsem_p)

运行示例

$ gcc example.c thpool.c -D THPOOL_DEBUG -pthread -o example

$ ./example
Making threadpool with 4 threads
THPOOL_DEBUG: Created thread 0 in pool
THPOOL_DEBUG: Created thread 1 in pool
THPOOL_DEBUG: Created thread 2 in pool
THPOOL_DEBUG: Created thread 3 in pool
Adding 40 tasks to threadpool
Thread #245600256 working on 0
Thread #246136832 working on 2
Thread #246673408 working on 3
Thread #246673408 working on 6
Thread #246673408 working on 7
...
Killing threadpool

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud学习笔记【初识微服务基础框架搭建】

文章目录微服务架构介绍架构图核心组件Spring Cloud版本对应基础框架搭建1.建造父工程2.建造子工程user工程建造auth工程建造RestTemplate 实现微服务远程调用RestTemplate 介绍配置RestTemplate测试远程访问总结微服务架构 介绍 微服务架构是一种将应用程序拆分成小型、自治…

设计模式之工厂模式

工厂模式是设计模式中的经典模式,工厂模式又可分为以下三种类型: 简单工厂模式工厂方法模式抽象工厂模式 这三种模式可以理解为同一种编程思想的三个版本,从简单到高级不断升级。本文将着重介绍简单工厂模式。 简单工厂模式 简单工厂模式&…

哈佛与冯诺依曼结构

1. 下图是典型的冯诺依曼结构 2. CPU分为三部分:ALU运算单元,CU控制单元,寄存器组。 3. 分析51单片机为何能使用汇编进行编程 51指令集(Instruction Set)是单片机CPU能够执行的所有指令的集合。在编写51单片机程序时&a…

Python打包成exe,文件太大问题解决办法(比保姆级还保姆级)

首先我要说一下,如果你不在乎大小,此篇直接别看了,因为我写过直接打包的,就多20M而已,这篇就别看了,点击查看不在乎大小直接打包这篇我觉得简单的令人发指 不废话,照葫芦画瓢就好 第1步&#…

Linux- 系统随你玩之--网络上的黑客帝国

文章目录1、前言2、TCPDump介绍2.1、问题来了: 所有用户都可以采用该命令吗?2.2、抓包原理2.3、特点2.3.1、参数化支持2.2.2、 TCP功能3、 服务器安装Tcpdump3.1、安装3.2、检查安装是否正常。4、tcpdump 命令4.1、常用功能选项4.2、输出内容5、实操5.1、…

初识C++需要了解的一些东西(2)

😁关注博主:翻斗花园第一代码手牛爷爷 😃Gitee仓库:牛爷爷爱写代码 目录🌍内联函数🌕内联函数概念🌖内联函数特性🌓auto关键字(C11)🌞类型别名⭐️auto简介☀️auto的使…

【数据库】数据库查询(进阶命令详解)

目录 1.聚合查询 1.1聚合函数 COUNT函数 SUM函数 AVG函数 MAX函数 MIN函数 1.2GROUP BY子句 1.3HAVING 2.联合查询 2.1内连接 2.2外连接 2.3自连接 2.4子查询 3.合并查询 写在前面: 文章截图均是每个代码显示的图。数据库对代码大小写不敏感&am…

java ArrayList源码分析(深度讲解)

ArrayList类的底层实现ArrayList类的断点调试空参构造的分步骤演示(重要)带参构造的分步骤演示一、前言大家好,本篇博文是对单列集合List的实现类ArrayList的内容补充。之前在List集合的万字详解篇,我们只是拿ArrayList演示了List…

C语言函数调用栈

栈溢出(stack overflow)是最常见的二进制漏洞,在介绍栈溢出之前,我们首先需要了解函数调用栈。 函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(caller)和被调用函数&#xf…

Java8使用Lambda表达式(流式)快速实现List转map 、分组、过滤等操作

利用java8新特性,可以用简洁高效的代码来实现一些数据处理。1 数据准备1.1 定义1个Fruit对象package com.wkf.workrecord.work;import org.junit.Test;import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;/*** author wuKeFan* date …

Stable Diffusion加chilloutmixni真人图片生成模型,AI绘图杀疯了

上期图文教程,我们分享过AI绘图大模型Stable Diffusion以及中文版本文心AI绘画大模型的基础知识以及代码实现,截至到目前为止。Stable Diffusion模型已经更新到了V2.1版本,其文生图大模型也越来越火,其在2022年底,由AI绘制的图片被荣为国际大奖,让大家对AI绘画大模型也越…

Node.js-----使用express写接口

使用express写接口 文章目录使用express写接口创建基本的服务器创建API路由模块编写GET接口编写POST接口CROS跨域资源共享1.接口的跨域问题2.使用cros中间件拒绝跨域问题3.什么是cros4.cros的注意事项5.cros请求的分类JSONP接口1.回顾jsonp的概念和特点2.创建jsonp接口的注意事…

请相信总有一扇门为你而开——社科院与杜兰大学金融管理硕士项目

考研人数每年都在递增,考研的竞争压力也逐年增长。考研话题也备受人们关注,初试,国家线,复试,考研的每一个关卡都会冲上热搜,引发热议。国家线公布后,有人欢喜有人忧。祝福成功上岸的学子们&…

【Leetcode——排序的循环链表】

😊😊😊 文章目录一、力扣题之排序循环链表二、解题思路1. 使用双指针法2、找出最大节点,最大节点的下一个节点是最小节点,由此展开讨论总结一、力扣题之排序循环链表 题目如下:航班直达!&#…

有什么比较好的bug管理工具?5款热门工具推荐

工具再优秀,适合自己才最重要。 为尽量讲透这个问题,本文的行文结构我先整理如下: 1、为什么需要bug管理工具? 2、好的bug管理工具的标准是什么? 3、好的bug管理工具推荐(5款) 4、如何挑选适合…

雪花算法(SnowFlake)

简介现在的服务基本是分布式、微服务形式的,而且大数据量也导致分库分表的产生,对于水平分表就需要保证表中 id 的全局唯一性。对于 MySQL 而言,一个表中的主键 id 一般使用自增的方式,但是如果进行水平分表之后,多个表…

【python实操】用python写软件弹窗

文章目录前言组件label 与 多行文本复选框组件Radiobutton单选组件Frame框架组件labelframe标签框架列表框Listboxscrollbar滚动条组件scale刻度条组件spinbox组件Toplevel子窗体组件PanedWindow组件Menu下拉菜单弹出菜单总结针对组件前言 python学习之路任重而道远&#xff0…

chatgpt这么火?前端如何实现类似chatgpt的对话页面

📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言📝…

代码看不懂?ChatGPT 帮你解释,详细到爆!

偷个懒,用ChatGPT 帮我写段生物信息代码如果 ChatGPT 给出的的代码不太完善,如何请他一步步改好?网上看到一段代码,不知道是什么含义?输入 ChatGPT 帮我们解释下。生信宝典 1: 下面是一段 Linux 代码,请帮…

Linux命令之nano命令

一、nano命令简介 nano是一个小型、免费、友好的编辑器,旨在取代非免费Pine包中的默认编辑器Pico。nano不仅复制了Pico的外观,还实现了Pico中一些缺失(或默认禁用)的功能,例如“搜索和替换”和“转到行号和列号”。nan…
最新文章