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 ...
随机推荐
- 解决Win7、Win10登录远程桌面连接时报错、提示“要求的函数不受支持”问题
解决Win7.Win10登录远程桌面连接时报错.提示"要求的函数不受支持"问题远程登录主机时地址,用户名,密码均正确,但是提示错误"要求的函数不受支持": 解决 ...
- FCC 高级算法题 库存更新
Inventory Update 依照一个存着新进货物的二维数组,更新存着现有库存(在 arr1 中)的二维数组. 如果货物已存在则更新数量 . 如果没有对应货物则把其加入到数组中,更新最新的数量. ...
- Android 自定义SeekBar (二)
一.前言 本文在 上节 的基础上,讲解自定义拖动条的实现思路. 二.思路 先在res/values文件夹下,自定义控件属性: <?xml version="1.0" enco ...
- 微信小程序从0到上线(一)环境搭建
前言: 专业术语: 步骤: 1.注册开发者账号:https://mp.weixin.qq.com/cgi-bin/wx,按照提示注册即可,如果是公司身份注册,需要公对公打款验证.
- WCF使用post 提交
[OperationContract] [WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFor ...
- 【javascript】关于 canvas.toDataURL()
在工作中遇到了奇怪的问题,在此记录. 一.定义 canvas.toDataURL()方法是返回一个包含图片展示的 数据URL.可以使用 type 参数其类型,默认为 PNG格式,图片的分辨率为96dp ...
- FreeType 矢量字体 测试移植(1)
之前有做过 ascii 和汉字库的字体点阵在lcd上显示的例子,都是按照指定大小的字库的点阵来显示的,所以一但选定了字体文件后,就固定了大小,不可变化,当然也可以存放各种 大小的字体文件,但这样的话就 ...
- Oracle 取Group By 第一条
select *from (select emp.*,row_number() over(partition by deptno order by rownum) cn from emp)where ...
- React-Hook知识整理与总结
1.useState:让函数式组件拥有状态 2.useEffect:副作用,取代生命周期 3.useContext:跨组件共享数据 4.useCallback:性能优化 5.useMemo:性能优化 ...
- 2.面向对象基础-03Java数组
一.数组的创建和初始化 (一)创建数组: import java.util.*; import java.io.*; public class Main { public static void ma ...