C 语言版线程池
一、初始线程池
1.1 何为线程池?
我们先来打个比方,线程池就好像一个工具箱,我们每次需要拧螺丝的时候都要从工具箱里面取出一个螺丝刀来。有时候需要取出一个来拧,有时候螺丝多的时候需要多个人取出多个来拧,拧完自己的螺丝那么就会把螺丝刀再放回去,然后别人下次用的时候再取出来用。
说白了线程池就是相当于「提前申请了一些资源,也就是线程」,需要的时候就从线程池中取出线程来处理一些事情,处理完毕之后再把线程放回去。

1.2 为什么要使用线程池?
我们来思考一个问题,为什么需要线程池呢?假如没有线程池的话我们每次调用线程是什么样子的?
显然首先是先创建一个线程,然后把任务交给这个线程,最后把这个线程销毁掉。这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程是需要消耗时间的。
那么如果我们改用线程池的话,在程序运行的时候就会首先创建一批线程,然后交给线程池来管理。有需要的时候我们从线程池中取出线程用于处理任务,用完后我们再将其回收到线程池中,这样是不是就避免了每次都需要创建和销毁线程这种耗时的操作。
有人会说你使用线程池一开始就消耗了一些内存,之后一直不释放这些内存,这样岂不是有点浪费。其实这是类似于空间换时间的概念,我们确实多占用了一点内存但是这些内存和我们珍惜出来的这些时间相比,是非常划算的。
池的概念是一种非常常见的空间换时间的概念,除了有线程池之外还有进程池、内存池等等。其实他们的思想都是一样的就是我先申请一批资源出来,然后就随用随拿,不用再放回来。
1.3 如何设计线程池
线程池的组成主要分为 3 个部分,这三部分配合工作就可以得到一个完整的线程池:
- 任务队列:存储需要处理的任务,由工作的线程来处理这些任务。
- 通过线程池提供的 API 函数,将一个待处理的任务添加到任务队列,或者从任务队列中删除
- 已处理的任务会被从任务队列中删除
- 线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程
- 工作的线程(任务队列任务的消费者):若干个,一般情况下根据 CPU 的核数来确定。
- 线程池中维护了一定数量的工作线程,他们的作用是不停的读任务队列,从里边取出任务并处理
- 工作的线程相当于是任务队列的消费者角色
- 如果任务队列为空,工作的线程将会被阻塞 (使用条件变量 / 信号量阻塞)
- 如果阻塞之后有了新的任务,由生产者将阻塞解除,工作线程开始工作
- 管理者线程(不处理任务队列中的任务):1 个
- 它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测
- 当任务过多的时候,可以适当的创建一些新的工作线程
- 当任务过少的时候,可以适当的销毁一些工作的线程
二、C 语言版线程池
由于本篇是对线程池的简单介绍,所以简化了一下线程池的模型,将 1.3 中的「3. 管理者线程」的角色给去除了。
2.1 结构体定义
2.1.1 任务结构体
/* 任务结构体 */
typedef struct
{
void (*function)(void *);
void *arg;
} threadpool_task_t;
2.1.2 线程池结构体
/* 线程池结构体 */
typedef struct
{
pthread_mutex_t lock; // 线程池锁,锁整个的线程池
pthread_cond_t notify; // 条件变量,用于告知线程池中的线程来任务了
int thread_count; // 线程池中的工作线程总数
pthread_t *threads; // 线程池中的工作线程
int started; // 线程池中正在工作的线程个数
threadpool_task_t *queue; // 任务队列
int queue_size; // 任务队列能容纳的最大任务数
int head; // 队头 -> 取任务
int tail; // 队尾 -> 放任务
int count; // 任务队列中剩余的任务个数
int shutdown; // 线程池状态, 0 表示线程池可用,其余值表示关闭
} threadpool_t;
thread_count 和 started 的区别:
- 初始化线程池的时候会创建一批线程(假设创建 n 个),此时 thread_count = started = n
- 当线程池运行过程中可能需要关闭一些线程(假设关闭 m 个),则会销毁这些线程,并 started -= n,但 thread_count 保持不变
- 即 thread_count 表示线程池中的申请的线程个数,而 started 表示当前能用的线程个数
shutdown 的作用:如果需要销毁线程池,那么必须要现将所有的线程退出才可销毁,而 shutdown 就是用于告知正在工作中的线程,线程池是否关闭用的。关闭方式又分为两种:一种是立即关闭,即不管任务队列中是否还有任务;另一种是优雅的关闭,即先处理完任务队列中的任务后再关闭。这两种方式可通过设置 shutdown 的不同取值即可实现:
typedef enum
{
immediate_shutdown = 1, // 立即关闭线程池
graceful_shutdown = 2 // 等线程池中的任务全部处理完成后,再关闭线程池
} threadpool_shutdown_t;
2.2 函数定义
2.2.1 ThreadPool_Init
函数原型:int ThreadPool_Init(int thread_count, int queue_size, threadpool_t **ppstThreadPool);
头 文 件:#include "ThrdPool.h"
函数功能:初始化线程池
参数描述:
- thread_count:入参,代表此次创建的线程池中的线程个数
- queue_size:入参,代表任务队列大小
- ppstThreadPool:出参,如果创建成功,则代表创建好的线程池,否则为 NULL
返 回 值:成功返回 E_SUCCEED,失败返回 E_ERROR
2.2.2 ThreadPool_Dispatch
函数原型:int ThreadPool_Dispatch(threadpool_t *pstThreadPool, void (*function)(void *), void *arg);
头 文 件:#include "ThrdPool.h"
函数功能:向线程池的任务队列中分发任务
参数描述:
- pstThreadPool:入参,代表创建好的线程池
- function:入参,表示任务
- arg:入参,代表 function 的参数
返 回 值:成功返回 E_SUCCEED,失败返回 E_ERROR
2.2.2 Threadpool_Destroy
函数原型:void Threadpool_Destroy(threadpool_t *pool, threadpool_shutdown_t shutdown_mode);
头 文 件:#include "ThrdPool.h"
函数功能:销毁线程池
参数描述:
- pool:入参,表示需要销毁的线程池
- shutdown_mode:入参,表示关闭模式,有两种取值
2.3 源码
2.3.1 ThrdPool.h
#ifndef __THRDPOOL_H__
#define __THRDPOOL_H__
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define DEBUG(format, args...) \
printf("[%s:%d] "format"\n", \
__FILE__, \
__LINE__, \
##args)
#define MAX_THREADS 16 // 线程池最大工作线程个数
#define MAX_QUEUE 256 // 线程池工作队列上限
#define E_SUCCEED 0
#define E_ERROR 112
#define SAFE_FREE(ptr) \
if (ptr) \
{ \
free(ptr); \
ptr = NULL; \
}
/* 任务结构体 */
typedef struct
{
void (*function)(void *);
void *arg;
} threadpool_task_t;
/* 线程池结构体 */
typedef struct
{
pthread_mutex_t lock; // 线程池锁,锁整个的线程池
pthread_cond_t notify; // 条件变量,用于告知线程池中的线程来任务了
int thread_count; // 线程池中的工作线程总数
pthread_t *threads; // 线程池中的工作线程
int started; // 线程池中正在工作的线程个数
threadpool_task_t *queue; // 任务队列
int queue_size; // 任务队列能容纳的最大任务数
int head; // 队头 -> 取任务
int tail; // 队尾 -> 放任务
int count; // 任务队列中剩余的任务个数
int shutdown; // 线程池状态, 0 表示线程池可用,其余值表示关闭
} threadpool_t;
typedef enum
{
immediate_shutdown = 1, // 立即关闭线程池
graceful_shutdown = 2 // 等线程池中的任务全部处理完成后,再关闭线程池
} threadpool_shutdown_t;
int ThreadPool_Init(int thread_count, int queue_size, threadpool_t **ppstThreadPool);
int ThreadPool_Dispatch(threadpool_t *pstThreadPool, void (*function)(void *), void *arg);
void Threadpool_Destroy(threadpool_t *pool, threadpool_shutdown_t shutdown_mode);
#endif
2.3.2 ThrdPool.c
#include <stdio.h>
#include "ThrdPool.h"
#define THREAD_COUNT 4
#define QUEUE_SIZE 128
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化锁,用于保证第16行的完整输出
void func(void *arg)
{
static int num = 0;
pthread_mutex_lock(&mutex);
// 为方便观察,故特意输出该语句,并使用num来区分不同的任务
DEBUG("这是执行的第 %d 个任务", ++num);
usleep(100000); // 模拟任务耗时,100ms
pthread_mutex_unlock(&mutex);
return;
}
int main()
{
int iRet;
threadpool_t *pool;
iRet = ThreadPool_Init(THREAD_COUNT, QUEUE_SIZE, &pool);
if (iRet != E_SUCCEED)
{
return 0;
}
int i;
for (i = 0; i < 20; i++) // 生产者,向任务队列中塞入 20 个任务
{
ThreadPool_Dispatch(pool, func, NULL);
}
usleep(500000);
// Threadpool_Destroy(pool, immediate_shutdown); // 立刻关闭线程池
Threadpool_Destroy(pool, graceful_shutdown); // 等任务队列中的任务全部执行完毕再关闭
return 0;
}
2.4 Tutorial
2.4.1 目录结构

2.4.2 编译、运行

参考资料
C 语言版线程池的更多相关文章
- python low版线程池
1.low版线程池设计思路:运用队列queue 将线程类名放入队列中,执行一个就拿一个出来import queueimport threading class ThreadPool(object): ...
- 使用 LinkedBlockingQueue 实现简易版线程池
前一阵子在做联系人的导入功能,使用POI组件解析Excel文件后获取到联系人列表,校验之后批量导入.单从技术层面来说,导入操作通常情况下是一个比较耗时的操作,而且如果联系人达到几万.几十万级别,必须拆 ...
- C语言实现线程池功能
1. 线程池基本原理 2. 线程池C语言实现 2.1 线程池的数据结构 #include <stdio.h> #include <pthread.h> #include < ...
- C语言实现线程池
以前写过一篇关于如何使用多线程推升推送速度(http://www.cnblogs.com/bai-jimmy/p/5177433.html),能够到达5000qps,其实已经可以满足现在的业务,不过在 ...
- go语言实现线程池
话说真的好久没有写博客了,最近赶新项目,工作太忙了.这一周任务比较少,又可以随便敲敲了. 逛论坛的时候突发奇想,想用go语言实现一个线程池,主要功能是:添加total个任务到线程池中,线程池开启num ...
- 用go语言实现线程池
代码放在 https://github.com/bigben0123/workerPool 安装完go软件后.执行目录中的install.cmd即可.
- python第五课——自定义线程池
内容概要: 1.low版线程池 2.绝版线程池 1.low版线程池 设计思路:运用队列queue 将线程类名放入队列中,执行一个就拿一个出来 import queue import threading ...
- python---基础知识回顾(十)进程和线程(py2中自定义线程池和py3中的线程池使用)
一:自定义线程池的实现 前戏: 在进行自定义线程池前,先了解下Queue队列 队列中可以存放基础数据类型,也可以存放类,对象等特殊数据类型 from queue import Queue class ...
- Python之实现不同版本线程池
1.利用queue和threading模块可以实现多个版本的线程池,这里先贴上一个简单的 import queue import time import threading class ThreadP ...
- python 五——自定义线程池
内容概要: 1.low版线程池 2.绝版线程池 1.low版线程池 设计思路:运用队列queue 将线程类名放入队列中,执行一个就拿一个出来 import queue import threading ...
随机推荐
- 十大经典排序之计数排序(C++实现)
计数排序 核心思想:计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中. 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数. 思路: 找出待排序的数组中最大和 ...
- PHP实现JWT登录鉴权
一.什么是JWT 1.简介 JWT(JSON Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准. 简单的说,JWT就是一种Token的编码算法,服务器端负责根据一个 ...
- MAVEN实践经验
1安装与配置 jdk: 1.6或以上 下载MAVEN3.x版本,解压后放在随便一目录,然后在系统环境变量配置MAVEN路径. 运行cmd-->输入 mvn -version 会出现maven版本 ...
- 3.Vue常用特性
1.表单操作 (1)基于Vue的表单操作 input 单行文本 处理方式就是使用 v-model双向绑定data中的数据 1 <input type="text" v-mod ...
- 搭建Angular基础项目学习
https://stackblitz.com/借助StackBlitz网站可快速开始搭建一个angular项目 一个angular的component包含三项东西 A component class ...
- hhtp协议和html标签分类css
HTTP协议四大特性: 1基于请求响应 2 基于tcp/ip协议之上的应用层协议 3 无状态 不能保存用户信息 4 无链接,短链接 二 get和post的区别? 1 get 不安全,get请求没有请求 ...
- Appium--滑动屏幕、不常用API
1.滑动屏幕api #滑动屏幕 size = driver.get_window_size() #获取屏幕大小 width = size.get('width') #宽 height = size.g ...
- MQ异常断开
ActiveMQ:No operations allowed after statement closed问题及解决办法 ActiveMQ版本:5.5.1 现象: 系统现象:部分消息发送失败,失败 ...
- Mysql 行号+分组行号+取Top记录 SQL
Mysql 行号+分组行号+取Top记录 SQL select * from ( SELECT (@rowNum := @rowNum + 1) as rowNum -- 全量行号 , a.col1 ...
- Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.1:test (default-test) on
解决错误 Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.1:test (default-test ...