0

0

Linux C下线程池的使用

WBOY

WBOY

发布时间:2024-02-13 11:33:27

|

715人浏览过

|

来源于良许Linux教程网

转载

你是否经常遇到服务器负载过高,无法正常访问的问题?那么,你需要了解 linux 系统中一个非常重要的概念——线程池。通过合理配置线程池,可以有效地避免服务器过载的问题,提高系统的稳定性和可靠性。

线程池也是多线程的处理方式。是将“生产者”线程提出任务添加到“任务队列”,然后一些线程自动完成“任务队列”上的任务。

Linux C下线程池的使用

多线程编程,创建一个线程,指定去完成某一个任务,等待线程的退出。虽然能够满足编程需求,但是当我们需要创建大量的线程的时候,在创建过程以及销毁线程的过程中可能会消耗大量的CPU.增加很大开销。如:文件夹的copy、WEB服务器的响应。

线程池就是用来解决类似于这样的一个问题的,可以降低频繁地创建和销毁线程所带来地开销。

线程池技术思路:一般采用预创建线程技术,也就是提前把需要用线程先创建一定数目。这些线程提前创建好了之后,“任务队列”里面假设没有任务,那么就让这些线程休眠,一旦有任务,就唤醒线程去执行任务,任务执行完了,也不需要去销毁线程,直到当你想退出或者是关机时,这个时候,那么你调用销毁线程池地函数去销毁线程。

线程完成任务之后不会销毁,而是自动地执行下一个任务。而且,当任务有很多,你可以有函数接口去增加线程数量,当任务较少时,你可以有函数接口去销毁部分线程。

如果,创建和销毁线程的时间对比执行任务的时间可以忽略不计,那么我们在这种情况下面也就没有必要用线程池。

“任务队列”是一个共享资源“互斥访问”

SLStuan手拉手团购程序(仿拉手网)
SLStuan手拉手团购程序(仿拉手网)

手拉手团购系统是一套Groupon模式的开源团购程序,是骏商网络(dream3.cn)旗下开发的一套仿拉手网团购程序,是国内首套采用ASP+MSSQL开发的团购程序,安装超简,功能超全面,在保留手拉手团购系统版权的前提下,允许所有用户永久免费使用、永久免费升级。手拉手团购系统内置多种主流在线支付接口,所有网银用户均可无障碍支付;短信发送团购券和实物团购快递发货等。 SLStuan手拉手团购程序 v

下载
Linux C下线程池的使用

线程池本质上也是一个数据结构,需要一个结构体去描述它:

struct pthread_pool //线程池的实现 
{ 
 //一般会有如下成员 
 
 //互斥锁,用来保护这个“任务队列” 
 pthread_mutex_t lock; //互斥锁  
  
 //线程条件变量 表示“任务队列”是否有任务 
 pthread_cond_t cond; //条件变量 
  
 bool shutdown; //表示是否退出程序 bool:类型 false / true 
 
 //任务队列(链表),指向第一个需要指向的任务 
 //所有的线程都从任务链表中获取任务 "共享资源" 
 struct task * task_list; 
  
 //线程池中有多个线程,每一个线程都有tid, 需要一个数组去保存tid 
 pthread_t * tids; //malloc()  
  
 //线程池中正在服役的线程数,当前线程个数 
 unsigned int active_threads; 
  
 //线程池任务队列最大的任务数量 
 unsigned int max_waiting_tasks; 
  
 //线程池任务队列上当前有多少个任务 
 unsigned int cur_waiting_tasks; 
  
 //...... 
 
}; 
 
//任务队列(链表)上面的任务结点,只要能够描述好一个任务就可以了, 
//线程会不断地任务队列取任务 
struct task  //任务结点  
{ 
 // 1. 任务结点表示的任务,“函数指针”指向任务要执行的函数(cp_file) 
 void*(* do_task)(void * arg); 
  
 //2. 指针,指向任务指向函数的参数(文件描述符) 
 void * arg; 
  
 //3. 任务结点类型的指针,指向下一个任务 
 struct task * next; 
}; 

线程池框架代码如下,功能自填:

操作线程池所需要的函数接口:pthread_pool.c 、pthread_pool.h

把“线程池”想象成一个外包公司,你需要去完成的就是操作线程池所提供的函数接口。

pthread_pool.c

#include "pthread_pool.h" 
 
/* 
 init_pool: 线程池初始化函数,初始化指定的线程池中有thread_num个初始线程 
 @pool:指针,指向您要初始化的那个线程池 
 @threa_num: 您要初始化的线程池中开始的线程数量 
 返回值:  
  成功 0 
  失败 -1 
*/ 
 
int init_pool(pthread_pool * pool , unsigned int threa_num) 
{ 
 //初始化线程池的结构体 
  
 //初始化线程互斥锁 
 pthread_mutex_init(&pool->lock, NULL); 
  
 //初始化线程条件变量 
 pthread_cond_init(&pool->cond, NULL); 
 
 pool->shutdown = false ;// 不退出 
 
 pool->task_list = (struct task*)malloc(sizeof(struct task)); 
 
 pool->tids = (pthread_t *)malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS); 
 if(pool->task_list == NULL || pool->tids == NULL) 
 { 
  perror("malloc memery error"); 
  return -1; 
 } 
 
 pool->task_list->next = NULL; 
 
 //线程池中一开始初始化多少个线程来服役 
 pool->active_threads = threa_num; 
 
 //表示线程池中最多有多少个任务 
 pool->max_waiting_tasks = MAX_WAITING_TASKS; 
 
 //线程池中任务队列当前的任务数量 
 pool->cur_waiting_tasks = 0; 
 
 //创建thread_num个线程,并且让线程去执行任务调配函数, 
 //记录所有线程的tid 
 int i = 0; 
 for(i = 0; i tids)[i], NULL, routine, (void*)pool); 
  if(ret != 0) 
  { 
   perror("create thread error"); 
   return -1; 
  } 
 
  printf("[%lu]:[%s] ===> tids[%d]:[%lu]",pthread_self(), 
   __FUNCTION__, i , pool->tids[i]); 
 } 
 
 return 0; 
} 
 
/* 
 routine: 任务调配函数。 
  所有线程开始都执行此函数,此函数会不断的从线程池的任务队列 
  中取下任务结点,去执行。 
   
  任务结点中包含“函数指针” h "函数参数" 
*/ 
 
void * routine(void * arg) 
{ 
 //arg表示你的线程池的指针 
  
 while() 
 { 
  //获取线程互斥锁,lock  
   
  //当线程池没有结束的时候,不断地从线程池的任务队列取下结点 
  //去执行。 
   
  //释放线程互斥锁,unlock 
   
  //释放任务结点 
 } 
} 
 
/* 
 destroy_pool: 销毁线程池,销毁前要保证所有的任务已经完成 
*/ 
 
int destroy_pool(pthread_pool * pool) 
{ 
 //释放所有空间 等待任务执行完毕(join)。 
 //唤醒所有线程 
 //利用join函数回收每一个线程资源。 
} 
 
/* 
 add_task:给任务队列增加任务, 把do_task指向的任务(函数指针)和 
  arg指向的参数保存到一个任务结点,添加到pool任务队列中。 
   
 @pool : 您要添加任务的线程池 
 @do_task : 您需要添加的任务(cp_file) 
 @arg: 您要执行的任务的参数(文件描述符) 
*/ 
 
int add_task(pthread_pool *pool,void*(* do_task)(void * arg), void*arg) 
{ 
 //把第二个参数和第三个参数封装成struct task  
  
 //再把它添加到 pool->task 任务队列中去 
  
 //注意任务队列是一个共享资源 
  
 //假如任务后要唤醒等待的线程。 
} 
 
//如果任务多的时候,往线程池中添加线程  pthread_create 
int add_threads(pthread_pool * pool, unsigned int num); 
{ 
 //新创建num个线程,让每一个线程去执行线程调配函数 
  
 //将每一个新创建的线程tid,添加到pool-> tids  
} 
 
//如果任务少的时候,减少线程池中线程的数量 pthread_cancel join 
int remove_threads(pthread_pool * pool, unsigned int num) 
{ 
 //用pthread_cancel取消num个线程  
 //利用pthread_join函数去回收资源。 
} 

pthread_pool.h

#ifndef __PTHREAD_POOL_H__ 
#define __PTHREAD_POOL_H__ 
 
//表示线程池中最多有多少个线程 
#define MAX_ACTIVE_THREADS 20 
 
//表示线程池中最多有多少个任务 
#define MAX_WAITING_TASKS 1024 
 
//任务队列(链表)上面的任务结点,只要能够描述好一个任务就可以了, 
//线程会不断地任务队列取任务 
struct task  //任务结点  
{ 
 // 1. 任务结点表示的任务,“函数指针”指向任务要执行的函数(cp_file) 
 void*(* do_task)(void * arg); 
  
 //2. 指针,指向任务指向函数的参数(文件描述符) 
 void * arg; 
  
 //3. 任务结点类型的指针,指向下一个任务 
 struct task * next; 
}; 
 
struct pthread_pool //线程池的实现 
{ 
 //一般会有如下成员 
 
 //互斥锁,用来保护这个“任务队列” 
 pthread_mutex_t lock; //互斥锁  
  
 //线程条件变量 表示“任务队列”是否有任务 
 pthread_cond_t cond; //条件变量 
  
 bool shutdown; //表示是否退出程序 bool:类型 false / true 
 
 //任务队列(链表),指向第一个需要指向的任务 
 //所有的线程都从任务链表中获取任务 "共享资源" 
 struct task * task_list; 
  
 //线程池中有多个线程,每一个线程都有tid, 需要一个数组去保存tid 
 pthread_t * tids; //malloc()  
  
 //线程池中正在服役的线程数,当前线程个数 
 unsigned int active_threads; 
  
 //线程池任务队列最大的任务数量 
 unsigned int max_waiting_tasks; 
  
 //线程池任务队列上当前有多少个任务 
 unsigned int cur_waiting_tasks; 
  
 //...... 
 
}; 
 
/* 
 init_pool: 线程池初始化函数,初始化指定的线程池中有thread_num 
  个初始线程 
 @pool:指针,指向您要初始化的那个线程池 
 @threa_num: 您要初始化的线程池中开始的线程数量 
 返回值:  
  成功 0 
  失败 -1 
*/ 
 
int init_pool(pthread_pool * pool , unsigned int threa_num); 
 
/* 
 routine: 任务调配函数。 
  所有线程开始都执行此函数,此函数会不断的从线程池的任务队列 
  中取下任务结点,去执行。 
   
  任务结点中包含“函数指针” h "函数参数" 
*/ 
 
void * routine(void * arg); 
 
/* 
 destroy_pool: 销毁线程池,销毁前要保证所有的任务已经完成 
*/ 
 
int destroy_pool(pthread_pool * pool); 
 
/* 
 add_task:给任务队列增加任务, 把do_task指向的任务(函数指针)和 
  arg指向的参数保存到一个任务结点,添加到pool任务队列中。 
   
 @pool : 您要添加任务的线程池 
 @do_task : 您需要添加的任务(cp_file) 
 @arg: 您要执行的任务的参数(文件描述符) 
*/ 
 
int add_task(pthread_pool *pool,void*(* do_task)(void * arg), void*arg); 
 
//如果任务多的时候,往线程池中添加线程  pthread_create 
int add_threads(pthread_pool * pool, unsigned int num); 
 
 
//如果任务少的时候,减少线程池中线程的数量 pthread_cancel join 
int remove_threads(pthread_pool * pool, unsigned int num); 
 
#endif 

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

197

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

190

2025.07.04

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

536

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

22

2026.01.06

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1051

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

107

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

556

2025.12.29

c++空格相关教程合集
c++空格相关教程合集

本专题整合了c++空格相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.6万人学习

Git 教程
Git 教程

共21课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号