【线程系列之五】线程池介绍C语言
一、基本概念
1.1 概念
线程池(Thread Pool)是一种基于池化技术管理线程的机制,旨在减少线程创建和销毁的开销,提高系统资源的利用率,以及更好地控制系统中同时运行的线程数量。线程池通过预先创建一定数量的线程,并将这些线程放入一个“池”中,当有新任务到来时,不是立即创建新线程来执行,而是从线程池中取出一个空闲的线程来执行该任务。如果所有线程都在忙碌,则新任务会等待直到有线程变得空闲。
在C语言中,由于标准库(如C89/C99/C11等)不支持线程或线程池,因此通常需要使用第三方库如POSIX线程(pthread)来实现线程池。

1.2 应用场景【C语言】
C语言中的线程池应用场景与在其他编程语言中类似,主要包括以下几类:
-
高并发服务器: 在网络服务器中处理大量客户端请求。每个请求可以分配给一个线程池中的线程进行处理,以提高响应速度和吞吐量。
-
数据处理和计算密集型任务: 当需要处理大量数据或执行复杂的计算时,可以将任务分配到线程池中并行执行,以缩短总体执行时间。
-
资源密集型任务: 对于需要频繁访问共享资源(如数据库、文件系统等)的任务,使用线程池可以减少线程创建和销毁的开销,并可能通过更高效的资源管理来提高性能。
-
异步操作: 在需要执行非阻塞操作(如异步I/O、异步网络请求等)时,线程池可以用来处理这些异步操作的结果或回调。
-
定时任务和周期性任务: C语言本身不直接支持定时器和周期性任务,但你可以使用线程池中的线程来模拟这些功能,通过轮询或睡眠机制来检查时间并执行相应的任务。
1.3 实现线程池步骤
-
定义线程池结构: 包括线程数量、任务队列、互斥锁、条件变量等;
-
实现任务队列: 用于存储待执行的任务;
-
编写线程工作函数: 该函数将被多个线程同时执行,并从任务队列中取出任务进行处理;
-
初始化和管理线程池: 包括创建线程、启动线程、销毁线程等操作;
-
添加任务到线程池: 提供接口将新任务添加到任务队列中,并通知等待的线程。
由于C语言相对底层,因此实现这些功能需要更多的手动编码和对系统资源的管理。此外,你还需要考虑线程安全和性能优化等问题,第三方库可在C++、python中查找,避免从头实现线程池。
下文将具体说明C语言实现线程池的代码。
二、实现
2.1 定义线程池结构
首先,定义一个包含线程池所需所有信息的结构体,如线程数组、任务队列、互斥锁、条件变量等。
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h> #define MAX_THREADS 4 // 任务队列节点
typedef struct task { void (*function)(void*); void* arg; struct task* next;
} task_t; // 线程池结构体
typedef struct { pthread_t threads[MAX_THREADS]; // 线程数组 pthread_mutex_t queue_mutex; // 保护任务队列的互斥锁 pthread_cond_t queue_cond; // 任务队列的条件变量 bool stop; // 线程池停止标志 task_t* head; // 任务队列头指针 task_t* tail; // 任务队列尾指针 int thread_count; // 当前活跃线程数 int active_threads; // 最大活跃线程数(可配置)
} threadpool_t;
2.2 初始化线程池
实现一个函数来初始化线程池,包括创建线程、初始化同步等。
void* worker(void* arg) { threadpool_t* pool = (threadpool_t*)arg; while (true) { pthread_mutex_lock(&pool->queue_mutex); // 如果线程池已停止且没有任务,则退出循环 if (pool->stop && pool->head == NULL) { pthread_mutex_unlock(&pool->queue_mutex); break; } // 等待任务或线程池停止信号 while (!pool->stop && pool->head == NULL && pool->thread_count >= pool->active_threads) { pthread_cond_wait(&pool->queue_cond, &pool->queue_mutex); } // 取出任务(如果线程池未停止且队列不为空) task_t* task = NULL; if (!pool->stop && pool->head != NULL) { task = pool->head; pool->head = task->next; if (pool->head == NULL) { pool->tail = NULL; } pool->thread_count--; } pthread_mutex_unlock(&pool->queue_mutex); // 执行任务(如果任务不为空) if (task != NULL) { (*(task->function))(task->arg); free(task); // 释放任务节点内存(如果任务节点是动态分配的) } } return NULL;
} void threadpool_init(threadpool_t* pool, int active_threads) { pool->stop = false; pool->head = pool->tail = NULL; pool->thread_count = 0; pool->active_threads = active_threads; pthread_mutex_init(&pool->queue_mutex, NULL); pthread_cond_init(&pool->queue_cond, NULL); for (int i = 0; i < active_threads; i++) { pthread_create(&pool->threads[i], NULL, worker, pool); }
}
2.3 添加任务到线程池
实现一个函数来向线程池的任务队列中添加任务,并唤醒一个等待的线程(如果有的话)。
void threadpool_add_task(threadpool_t* pool, void (*function)(void*), void* arg) { task_t* new_task = (task_t*)malloc(sizeof(task_t)); new_task->function = function; new_task->arg = arg; new_task->next = NULL; pthread_mutex_lock(&pool->queue_mutex); if (pool->tail == NULL) { pool->head = pool->tail = new_task; } else { pool->tail->next = new_task; pool->tail = new_task; } pthread_cond_signal(&pool->queue_cond); pthread_mutex_unlock(&pool->queue_mutex);
}
2.4 销毁线程池
实现一个函数来停止所有线程并销毁线程池。
void threadpool_destroy(threadpool_t* pool) { pool->stop = true; pthread_cond_broadcast(&pool->queue_cond); for (int i = 0; i < MAX_THREADS; i++) { pthread_join(pool->threads[i], NULL); } pthread_mutex_destroy(&pool->queue_mutex); pthread_cond_destroy(&pool->queue_cond);
}
三、完整简单示例
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>#define MAX_THREADS 5typedef struct task {void (*function)(void*);void* arg;struct task* next;
} task_t;typedef struct {pthread_t threads[MAX_THREADS];pthread_mutex_t queue_mutex;pthread_cond_t queue_cond;bool stop;task_t* head;task_t* tail;int thread_count;int active_threads;
} threadpool_t;void* worker(void* arg) {threadpool_t* pool = (threadpool_t*)arg;while (true) {pthread_mutex_lock(&pool->queue_mutex);while (pool->head == NULL && !pool->stop) {pthread_cond_wait(&pool->queue_cond, &pool->queue_mutex);}if (pool->stop && pool->head == NULL) {pthread_mutex_unlock(&pool->queue_mutex);break;}task_t* task = pool->head;if (task != NULL) {pool->head = task->next;if (pool->head == NULL) {pool->tail = NULL;}}pthread_mutex_unlock(&pool->queue_mutex);if (task != NULL) {(*(task->function))(task->arg);free(task); // 释放任务节点内存}sleep(1);}return NULL;
}void threadpool_init(threadpool_t* pool, int active_threads) {pool->stop = false;pool->head = pool->tail = NULL;pool->thread_count = 0;pool->active_threads = active_threads;pthread_mutex_init(&pool->queue_mutex, NULL);pthread_cond_init(&pool->queue_cond, NULL);for (int i = 0; i < active_threads; ++i) {pthread_create(&pool->threads[i], NULL, worker, pool);}
}void threadpool_add_task(threadpool_t* pool, void (*function)(void*), void* arg) {task_t* new_task = (task_t*)malloc(sizeof(task_t));new_task->function = function;new_task->arg = arg;new_task->next = NULL;pthread_mutex_lock(&pool->queue_mutex);if (pool->tail == NULL) {pool->head = pool->tail = new_task;} else {pool->tail->next = new_task;pool->tail = new_task;}pthread_cond_signal(&pool->queue_cond);pthread_mutex_unlock(&pool->queue_mutex);
}void threadpool_destroy(threadpool_t* pool) {pool->stop = true;pthread_mutex_lock(&pool->queue_mutex);pthread_cond_broadcast(&pool->queue_cond);pthread_mutex_unlock(&pool->queue_mutex);for (int i = 0; i < pool->active_threads; ++i) {pthread_join(pool->threads[i], NULL);printf("%d\r\n", i);}pthread_mutex_destroy(&pool->queue_mutex);pthread_cond_destroy(&pool->queue_cond);
}// 示例任务函数
void example_task(void* arg) {int task_id = *(int*)arg;printf("Task %d is executing\n", task_id);free(arg); // 释放参数内存sleep(1); // 模拟任务执行时间
}int main() {threadpool_t pool;threadpool_init(&pool, 2); // 初始化线程池,最大活跃线程数为2// 添加示例任务for (int i = 0; i < MAX_THREADS; ++i) {int* arg = (int*)malloc(sizeof(int));*arg = i;threadpool_add_task(&pool, example_task, arg);}// 等待一段时间,观察任务执行情况sleep(5);// 销毁线程池threadpool_destroy(&pool);return 0;
}
运行结果为:

这段代码演示了一个简单的线程池的使用方式。在主函数中,我们初始化了一个线程池(最大活跃线程数为2),然后添加了5个示例任务(每个任务执行时间模拟为1秒)。在任务执行完毕后,通过调用 threadpool_destroy 函数来销毁线程池,确保所有任务都被执行完毕。
注意:这里销毁的时候保证知道数组中有多少个有效的线程ID,进而销毁。
可在结构体中添加pool->num_threads ,即用来保存线程池中线程数量的成员变量,应该在初始化线程池时进行设置,并在后续操作中根据需要使用,则不会出现在销毁时越界或死锁。
这个示例展示了如何使用线程池来管理并发执行的任务,以及如何通过互斥锁和条件变量来实现线程安全的任务队列操作。
相关文章:
【线程系列之五】线程池介绍C语言
一、基本概念 1.1 概念 线程池(Thread Pool)是一种基于池化技术管理线程的机制,旨在减少线程创建和销毁的开销,提高系统资源的利用率,以及更好地控制系统中同时运行的线程数量。线程池通过预先创建一定数量的线程&am…...
【学习css3】使用flex和grid实现等高元素布局
过往的实现方法是使用浮动加计算布局来实现,当flex和grid问世时,这一切将变得简单起来 一、简单的两列实现 1、先看页面效果 2、css代码 .container {padding: 10px;width: 100ch;margin: 0 auto;box-shadow: inset 0 0 0 2px #ccc;}.column {margin: 2…...
如何防止Eclipse格式化程序在行注释开头插入空格
格式化前: //foo bar 格式化后: // foo bar 这种看着不是很舒服。如果不让格式化时自动在注释符后面插入空格呢? 要在Eclipse中进行代码格式化时防止在行注释(//)后面自动增加空格,可以通过调整…...
Nextjs 调用组件内的方法
在 Next.js 中,如果你想从一个组件外部调用组件内部的方法,可以使用 React 的 useRef 钩子来引用组件实例并调用其方法。这种方法主要适用于类组件,但也可以用于函数组件,通过将方法暴露在 ref 对象上。 以下是一个示例ÿ…...
ip地址是电脑还是网线决定的
在数字化时代的浪潮中,网络已经成为了我们日常生活和工作不可或缺的一部分。当我们谈论网络时,IP地址无疑是一个核心的概念。然而,关于IP地址的分配和决定因素,很多人可能存在误解。有些人认为IP地址是由电脑决定的,而…...
Hadoop中HDFS、Hive 和 HBase三者之间的关系
HDFS(Hadoop Distributed File System)、Hive 和 HBase 是 Hadoop 生态系统中三个重要的组件,它们各自解决了大数据存储和处理的不同层面的问题。我们用大白话来解释这三个组件之间的关系: HDFS - 数据的仓库: HDFS 是…...
opencv—常用函数学习_“干货“_10
目录 二七、离散余弦变换 执行离散余弦变换 (dct) 和逆变换 (idct) 解释 实际应用 JPEG压缩示例(简化版) 二八、图像几何变换 仿射变换 (warpAffine 和 getAffineTransform) 透视变换 (warpPerspective 和 getPerspectiveTransform) 旋转变换 (g…...
Jmeter二次开发Demo
Jmeter二次开发Demo 前言 在上一集,我们已经完成了JMX脚本的分析,大致了解了JMX脚本的基本元素。 那么在这一集,我们将会介绍一下Jmeter二次开发的Demo。 Demo代码 那么话不多说,我们就直接上代码。 public class TestStress…...
MongoDB综合实战篇(超容易)
一、题目引入 在MongoDB的gk集合里插入以下数据: 用语句完成如下功能: (1)查询张三同学的成绩信息 (2)查询李四同学的语文成绩 (3)查询没有选化学的同学 (4…...
框架设计MVVM
重点: 1.viewmodel 包含model 2.view包含viewmodel,通过驱动viewmodel去控制model的数据和业务逻辑 // Test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream> #include <vector>using namespace std;#p…...
RK3399基础部分
1.RK3399介绍 基础特性: 高达1.8GHz的双核Cortex-A72 四核Cortex-A53高达1.4GHz NPU高达3.0TOPS Mali-T860MP4 GPU 双通道DDR3/DDR3L/LPDDR3/LPDDR4 4K超高清H265/H264/VP9 HDR10/HLG H264编码器 双MIPI CSI和ISP USB Type-CGPU: 图形处理器(英语&…...
linux高级编程(广播与组播)
广播与组播: 广播: 局域网,一个人发所有人都能收(服务器找客户端),(发给路由器的广播地址后后路由器自动给所有人发,可用于服务器找客户端) 只能udp来做 setsocketopt…...
Andriod Stdio新建Kotlin的Jetpack Compose简单项目
1.选择 No Activity 2.选择kotlin 4.右键选择 在目录MyApplication下 New->Compose->Empty Project 出现下面的画面 Finish 完成...
Linux多线程编程-哲学家就餐问题详解与实现(C语言)
在哲学家就餐问题中,假设有五位哲学家围坐在圆桌前,每位哲学家需要进行思考和进餐两种活动。他们的思考不需要任何资源,但进餐需要使用两根筷子(左右两侧各一根)。筷子是共享资源,哲学家们在进行进餐时需要…...
从C向C++18——演讲比赛流程管理系统
一.项目需求 1.比赛规则 学校举行一场演讲比赛,共有12个人参加。比赛共两轮,第一轮为淘汰赛,第二轮为决赛。每名选手都有对应的编号,如 10001~ 10012比赛方式:分组比赛,每组6个人;第一轮分为两…...
QThread和std::thread
在 Qt 中, 我们经常会用到多线程,这时候就需要纠结是使用 Qt 的 QThread 还是使用 C 标准库的 std::thread。 这里记录一下我自己的理解,先介绍一下 QThread 和 std::thread 的使用方法,对比一下他们的不同,最后说一下…...
LeetCode 算法:组合总和 c++
原题链接🔗:组合总和 难度:中等⭐️⭐️ 题目 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 …...
【两大3D转换SDK对比】HOOPS Exchange VS. CAD Exchanger
在现代工业和工程设计领域,CAD数据转换工具是确保不同软件系统间数据互通的关键环节。HOOPS Exchange和CAD Exchanger是两款备受关注的工具,它们在功能、支持格式、性能和应用场景等方面有着显著差异。 本文将从背景、支持格式、功能和性能、应用场景等…...
Openerstry + lua + redis根据请求参数实现动态路由转发
文章目录 一、需求分析二、准备1、软件安装2、redis-lua封装优化 三、实现1、nginx.conf2、dynamic.lua注意 3、准备两个应用4、访问nginx 四、参数直接传要代理的地址端口 一、需求分析 根据用户访问url的参数,将请求转发到对应指定IP的服务器上。 二、准备 1、…...
数字名片-Pushmall 智能AI数字名片7月更新计划
[数字名片]-商务营销推广助手7月更新计划 数字名片-商务营销推广助手7月更新计划 **2024年 6月完成模块开发优化****实现SaaS框架业务 1、智能名片:创建个人名片、企业名片、商机管理。 2、人脉商圈:附近人脉、就近群脉、好友名片。 3、企微社群&…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
