一、初始线程池

1.1 何为线程池?

我们先来打个比方,线程池就好像一个工具箱,我们每次需要拧螺丝的时候都要从工具箱里面取出一个螺丝刀来。有时候需要取出一个来拧,有时候螺丝多的时候需要多个人取出多个来拧,拧完自己的螺丝那么就会把螺丝刀再放回去,然后别人下次用的时候再取出来用。

说白了线程池就是相当于「提前申请了一些资源,也就是线程」,需要的时候就从线程池中取出线程来处理一些事情,处理完毕之后再把线程放回去。

1.2 为什么要使用线程池?

我们来思考一个问题,为什么需要线程池呢?假如没有线程池的话我们每次调用线程是什么样子的?

显然首先是先创建一个线程,然后把任务交给这个线程,最后把这个线程销毁掉。这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程是需要消耗时间的。

那么如果我们改用线程池的话,在程序运行的时候就会首先创建一批线程,然后交给线程池来管理。有需要的时候我们从线程池中取出线程用于处理任务,用完后我们再将其回收到线程池中,这样是不是就避免了每次都需要创建和销毁线程这种耗时的操作。

有人会说你使用线程池一开始就消耗了一些内存,之后一直不释放这些内存,这样岂不是有点浪费。其实这是类似于空间换时间的概念,我们确实多占用了一点内存但是这些内存和我们珍惜出来的这些时间相比,是非常划算的。

池的概念是一种非常常见的空间换时间的概念,除了有线程池之外还有进程池、内存池等等。其实他们的思想都是一样的就是我先申请一批资源出来,然后就随用随拿,不用再放回来。

1.3 如何设计线程池

线程池的组成主要分为 3 个部分,这三部分配合工作就可以得到一个完整的线程池:

  1. 任务队列:存储需要处理的任务,由工作的线程来处理这些任务。

    • 通过线程池提供的 API 函数,将一个待处理的任务添加到任务队列,或者从任务队列中删除
    • 已处理的任务会被从任务队列中删除
    • 线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程
  2. 工作的线程(任务队列任务的消费者):若干个,一般情况下根据 CPU 的核数来确定。
    • 线程池中维护了一定数量的工作线程,他们的作用是不停的读任务队列,从里边取出任务并处理
    • 工作的线程相当于是任务队列的消费者角色
    • 如果任务队列为空,工作的线程将会被阻塞 (使用条件变量 / 信号量阻塞)
    • 如果阻塞之后有了新的任务,由生产者将阻塞解除,工作线程开始工作
  3. 管理者线程(不处理任务队列中的任务):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"

函数功能:初始化线程池

参数描述:

  1. thread_count:入参,代表此次创建的线程池中的线程个数
  2. queue_size:入参,代表任务队列大小
  3. 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"

函数功能:向线程池的任务队列中分发任务

参数描述:

  1. pstThreadPool:入参,代表创建好的线程池
  2. function:入参,表示任务
  3. 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"

函数功能:销毁线程池

参数描述:

  1. pool:入参,表示需要销毁的线程池
  2. 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 语言版线程池的更多相关文章

  1. python low版线程池

    1.low版线程池设计思路:运用队列queue 将线程类名放入队列中,执行一个就拿一个出来import queueimport threading class ThreadPool(object): ...

  2. 使用 LinkedBlockingQueue 实现简易版线程池

    前一阵子在做联系人的导入功能,使用POI组件解析Excel文件后获取到联系人列表,校验之后批量导入.单从技术层面来说,导入操作通常情况下是一个比较耗时的操作,而且如果联系人达到几万.几十万级别,必须拆 ...

  3. C语言实现线程池功能

    1. 线程池基本原理 2. 线程池C语言实现 2.1 线程池的数据结构 #include <stdio.h> #include <pthread.h> #include < ...

  4. C语言实现线程池

    以前写过一篇关于如何使用多线程推升推送速度(http://www.cnblogs.com/bai-jimmy/p/5177433.html),能够到达5000qps,其实已经可以满足现在的业务,不过在 ...

  5. go语言实现线程池

    话说真的好久没有写博客了,最近赶新项目,工作太忙了.这一周任务比较少,又可以随便敲敲了. 逛论坛的时候突发奇想,想用go语言实现一个线程池,主要功能是:添加total个任务到线程池中,线程池开启num ...

  6. 用go语言实现线程池

    代码放在 https://github.com/bigben0123/workerPool 安装完go软件后.执行目录中的install.cmd即可.

  7. python第五课——自定义线程池

    内容概要: 1.low版线程池 2.绝版线程池 1.low版线程池 设计思路:运用队列queue 将线程类名放入队列中,执行一个就拿一个出来 import queue import threading ...

  8. python---基础知识回顾(十)进程和线程(py2中自定义线程池和py3中的线程池使用)

    一:自定义线程池的实现 前戏: 在进行自定义线程池前,先了解下Queue队列 队列中可以存放基础数据类型,也可以存放类,对象等特殊数据类型 from queue import Queue class ...

  9. Python之实现不同版本线程池

    1.利用queue和threading模块可以实现多个版本的线程池,这里先贴上一个简单的 import queue import time import threading class ThreadP ...

  10. python 五——自定义线程池

    内容概要: 1.low版线程池 2.绝版线程池 1.low版线程池 设计思路:运用队列queue 将线程类名放入队列中,执行一个就拿一个出来 import queue import threading ...

随机推荐

  1. 使用navicat连接本地数据库时,出现错误1251错误

    在安装完MySQL的时候,我们现在一般都使用Navicat来连接数据库,可惜出现下面的错误:1251-Client does not support authentication protocol r ...

  2. Hbase操作与编程使用

    1.任务: 列出HBase所有的表的相关信息,例如表名: 3. 编程完成以下指定功能(教材P92下): (1)createTable(String tableName, String[] fields ...

  3. redis缓存lua脚本过多导致内存占用很多问题

    现象 生产某集群各节点已使用内存比较大,在清理了大量业务无用数据后,节点已使用内存却未下降. 排查与分析 通过info memory命令查看,内存碎片率略高,但是对某个主节点新添加的从节点内存使用依然 ...

  4. 图算法之BFS

    深度优先搜索(Breadth  First Search),类似于树的层序遍历,搜索模型是队列,还是以下面的无向图为例: 实验环境是Ubuntu 14.04 x86 伪代码实现如下: 其中u 为 v ...

  5. idea项目编译时报错:程序包XXX不存在

    问题如下: 解决方法: 点击File-->点击settings-->点击maven-->点击Runner-->勾选Delegate IDE build/run action t ...

  6. 2020.11.24 typeScript命名空间

    命名空间:定义了标识符的可见范围,一个标识符可以在多个命名空间中定义,它在不同命名空间的含义是互不相干的.在一个新的命名空间可以定义任何新的标识符,它不会与已有的任何标识符发生冲突. 使用: 这个时候 ...

  7. 手写一个简易的ajax

    function ajax(url,successFul){ const xhr=new XMLHttpRequest() xhr.open("Get",url,true) xhr ...

  8. 用VUE框架开发的准备

    使用VUE框架编写项目的准备工作 防止我几天不打代码,忘记怎么打了 下载小乌龟拉取码云项目文件,用于码云仓库代码提交与拉取(可以不安装) 小乌龟要设置你的码云账号 密码 在控制面版 中 凭证里可以修改 ...

  9. Centos7.6操作系统安装

    新建虚拟机 默认下一步 稍后安装操作系统 选择对应的操作系统和版本 指定虚拟机名称和存储位置 处理器配置 内存配置:图形化界面至少2G,字符界面至少1G. 网络类型默认为NAT I/O控制器类型默认L ...

  10. ANOMALY: use of REX.w is meaningless (default operand size is 64)

    1.打开注册机:win+ R   输入regedit2.找到目录:计算机\HKEY_LOCAL_MACHINE\SOFTWARE\TEC\Ocular.3\agent\config 并添加值3.新增项 ...