手写线程池|C语言版(二)|定义线程池的结构、创建线程池实例

ops/2024/10/19 1:25:14/

文章目录

  • 定义线程池结构
    • 任务结构体
    • 定义线程池结构体
  • 组织头文件
  • 创建线程池实例
    • 函数原型定义
    • 线程池创建函数实现
      • 初始化线程池结构体指针
      • 初始化线程池结构体的各类参数
  • 定义线程池的结构C代码
  • 创建线程池总体C代码

本文中,我们将创建线程池的结构体,该结构体中包含了线程池所需要的全部数据类型,并且我们实现了创建线程池函数

前文回顾:

手写线程池|C语言版(一)|线程池的定义和运行逻辑

定义线程池结构

下列结构体定义在文件threadpool.c中

任务结构体

typedef struct Task
{void (*function)(void* arg);//这是一个函数指针和函数指针的参数void* arg;//表示可以接受任何类型的参数
}Task;

任务结构体中包含了函数指针和函数指针的参数。后续我们需要定义添加任务的函数,通过该函数实现任务结构体的初始化。

定义线程池结构体

struct ThreadPool
{//任务队列Task* taskQueue;	//任务队列int queueCapacity; //容量int queueSize;     //当前任务个数int queueFront;    //队头->取数据int queueTail;     //队尾->放数据// 管理者线程pthread_t managerID; //管理者线程IDpthread_t *threadIDs; //工作的线程IDint minNums;    //最小线程数int maxNums;    //最大线程数int busyNums;    //忙的线程数int liveNums;    //存活的线程int exitNums;   // 要杀死的线程个数//锁pthread_mutex_t mutexPool;  //锁整个的线程池pthread_mutex_t mutexBusy;  //锁busyNums,因为其变化比较频繁//条件变量pthread_cond_t notFull;     //任务队列是不是满了pthread_cond_t notEmpty;    //任务队列是不是空了int shutdown; //是不是要销毁线程池,销毁为1,不销毁为0
};

组织头文件

我们需要在头文件中写入线程池各个操作的函数原型

#ifndef _THREADPOOL_H
#define _THREADPOOL_H
//创建线程池并初始化//销毁线程池//给线程池添加任务//获取线程池中工作的线程的个数//获取线程池中活着的线程的个数#endif //_THREADPOOL_H

创建线程池实例

在本节中,我们的目标是创建一个线程池实例,首先需要在头文件中定义创建线程池的函数原型

函数原型定义

我们先分析一下,初始化一个线程池需要什么参数呢?

首先我们初始化的线程池中有需要维护的最小进程,然后我们需要让线程池知道它的最大进程数是多少,进程池中有一个任务队列来存储任务,构建任务队列我们必须要知道任务队列的尺寸

综上所述:我们需要参数minNumThread, maxNumberThread, queueSize

既然是为了创建一个线程池实例,我们的返回值当然就是该线程池的地址ThreadPool*

#ifndef _THREADPOOL_H
#define _THREADPOOL_Htypedef struct ThreadPool ThreadPool;
//创建线程池
ThreadPool* threadPoolCreate(int min, int max, int queueSize)#endif //_THREADPOOL_H

线程池创建函数实现

初始化线程池结构体指针

我们首先需要初始化一个ThreadPool* pool出来,因为pool在后续代码中,不仅要初始化其中的各类数据结构,还需要将pool返回给调用者,完成线程池的创建。

ThreadPool *threadPoolCreate(int min, int max, int queueSize)
{   //如果初始化操作没问题,就应该返回该线程池的结构体指针ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));if (pool == NULL){printf("malloc threadpool fail...\n");return NULL;}...return pool;
}

初始化线程池结构体的各类参数

  • 为工作线程分配空间
    由于我们需要通过管理者线程来对工作线程进行增加或者减少的操作,所以我们为工作线程分配堆空间,方便管理。
		pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t)* max);if (pool->threadIDs == NULL){printf("malloc threadIDs fail...\n");return NULL;}memset(pool->threadIDs, 0, sizeof(pthread_t)* max);
  • 初始化线程池的各个重要参数
        pool->minNums = minNumThread;pool->maxNums = maxNumThread;pool->busyNums = 0;pool->liveNums = minNumThread; //刚开始和最小个数相等pool->exitNums = 0;
  • 初始化锁和条件变量
        if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||pthread_cond_init(&pool->notEmpty, NULL) != 0||pthread_cond_init(&pool->notFull, NULL) != 0){printf("mutex or cond failed ...\n");return NULL;}
  • 初始化任务队列
        //任务队列pool->taskQ = (Task*)malloc(sizeof(Task)* queueSize);pool->queueCapacity = queueSize;pool->queueSize = 0;pool->queueFront = 0;pool->queueTail = 0;pool->shutdown = 0;
  • 创建管理者线程和工作线程

在这里管理者线程和工作线程的回调函数,我们会留到后面章节去实现,首先需要明确这两个回调函数的参数。

还记得管理者线程和工作者线程的任务吗?
手写线程池|C语言版(一)|线程池的定义和运行逻辑

很明显,其中manager需要按策略来添加、销毁进程,需要的参数显然包括我们线程池中的大部分参数,所以我们传入pool;

对于worker,我们需要它能够从任务队列中取任务,任务队列及其相关的数据我们都定义在了struct ThreadPool结构体中,所以我们仍然选择传入pool

        //创建线程pthread_create(&pool->managerID, NULL, manager, pool);//管理者线程for (int i = 0; i < minNumThread; i++){pthread_create(&pool->threadIDs[i], NULL, worker, pool);//工作线程}

初始化上述结构后,我们应当返回return pool
然而,我们忽视了一个很大的问题,由于我们malloc了好几块内存用于为threadIDspool分配堆内存,但是一旦分配失败,我们应该释放他们占用的空间,不然他们会成为野指针,可能造成内存泄漏。
但是呢,一个一个为他们free比较麻烦,这里推荐一种结构,来增强代码的健壮性。

do{	//把之前分配内存或者其他操作导致的return NULL统一改写成break//在函数块的最后进行后续的处理工作(包括内存释放和返回空指针)a = malloc();if (false){break;}b = malloc();if (false){break;}c = malloc();if (false){break;}
}while(0)
free(a);
free(b);
free(c);
return NULL:

定义线程池的结构C代码

typedef struct Task
{void (*function)(void* arg);//这是一个函数指针和函数指针的参数void* arg;//表示可以接受任何类型的参数//这里函数参数是个范型就是借鉴的pthread_creat()函数的第三个参数
}Task;struct ThreadPool
{Task* taskQ;int queueCapacity; //容量int queueSize;     //当前任务个数int queueFront;    //队头->取数据int queueTail;     //队尾->放数据pthread_t managerID; //管理者线程IDpthread_t *threadIDs; //工作的线程IDint minNums;    //最小线程数int maxNums;    //最大线程数int busyNums;    //忙的线程数int liveNums;    //存活的线程int exitNums;   // 要杀死的线程个数pthread_mutex_t mutexPool;  //锁整个的线程池pthread_mutex_t mutexBusy;  //锁busyNums,因为其变化比较频繁pthread_cond_t notFull;     //任务队列是不是满了pthread_cond_t notEmpty;    //任务队列是不是空了int shutdown; //是不是要销毁线程池,销毁为1,不销毁为0
};

创建线程池总体C代码

(共50行不到)

ThreadPool *threadPoolCreate(int min, int max, int queueSize)
{   //如果初始化操作没问题,就应该返回该线程池的结构题ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));do{if (pool == NULL){printf("malloc threadpool fail...\n");break;}pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t)* max);if (pool->threadIDs == NULL){printf("malloc threadIDs fail...\n");break;}memset(pool->threadIDs, 0, sizeof(pthread_t)* max);pool->minNums = min;pool->maxNums = max;pool->busyNums = 0;pool->liveNums = min; //刚开始和最小个数相等pool->exitNums = 0;if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||pthread_cond_init(&pool->notEmpty, NULL) != 0||pthread_cond_init(&pool->notFull, NULL) != 0){printf("mutex or cond failed ...\n");break;}//任务队列pool->taskQ = (Task*)malloc(sizeof(Task)* queueSize);pool->queueCapacity = queueSize;pool->queueSize = 0;pool->queueFront = 0;pool->queueTail = 0;pool->shutdown = 0;//创建线程pthread_create(&pool->managerID, NULL, manager, pool);//管理者线程for (int i = 0; i < min; i++){pthread_create(&pool->threadIDs[i], NULL, worker, pool);//工作线程}return pool;}while(0);// 释放资源if (pool && pool->threadIDs) free(pool->threadIDs);if (pool && pool->taskQ) free (pool->taskQ);if (pool) free(pool);return NULL;}

http://www.ppmy.cn/ops/20877.html

相关文章

解决VMware启动异常

问题1&#xff1a;该虚拟机似乎正在使用中。如果该虚拟机未在使用&#xff0c;请按“获取所有权(T)”按钮获 取它的所有权。否则&#xff0c;请按“取消(C)”按钮以防损坏。 解决步骤&#xff1a; 按弹框提示的配置文件目录下删除后缀为lck的文件&#xff08;lock&#xff09;。…

C++下跨平台websocket库及使用示例

websocketpp库使用非常方便&#xff0c;开源&#xff0c;可跨平台&#xff0c;内部实现全部是head文件&#xff0c;无cpp&#xff0c;接口简单易用。源码路径比如 https://github.com/zaphoyd/websocketpp.git 使用方法是&#xff0c;先下载源码包并复制道工程的include目录下…

web前端框架设计第七课-事件处理

web前端框架设计第七课-事件处理 一.预习笔记 1.使用v-on指令实现事件监听 2.事件指令中内联JS代码 3.事件修饰符(对事件的处理&#xff0c;例如向上冒泡) 4.按键修饰符(有的浏览器对于enter键默认为提交) 二.课堂笔记 三.课后回顾 –行动是治愈恐惧的良药&#xff0c;犹豫拖…

Stable Diffusion入门指南

SD 保姆教程&#xff0c;从原理功能到案例输出展示&#xff0c;最后简述 ControlNet 的使用技巧。 Stable Diffusion 的基本介绍 Stable Diffusion是一种基于扩散过程的图像生成模型&#xff0c;可以生成高质量、高分辨率的图像。它通过模拟扩散过程&#xff0c;将噪声图像逐…

【AI学习】Ben Thompson对 Nat Friedman 和 Daniel Gross的采访

读了Ben Thompson对 Nat Friedman 和 Daniel Gross的采访这篇文章&#xff0c;挺有趣。 先说最有趣的几个地方&#xff1a; 关于维苏威火山碳化古卷挑战&#xff1a;有趣而有意义&#xff01;有关日本的预测&#xff1a;有可能依靠芯片制造业&#xff0c;也许 2030 年代就又是…

【SHADER】Shader实例学习1:UI noise halo

Shader实例学习1&#xff1a;UI noise halo 简介源码注释关键步骤总结 简介 又换方向做图形相关的工作了&#xff0c;门槛确实有一点&#xff0c;数学、图形什么的都要学&#xff0c;算轨迹用到力学什么的也是基本操作。大力出奇迹&#xff0c;跟着shadertoy里的众多大神学起来…

Web3与传统互联网:挑战、融合与共生

引言 Web3技术正以惊人的速度改变着我们的互联网体验。作为一个基于区块链的去中心化互联网模型&#xff0c;Web3不仅带来了创新和变革&#xff0c;还引发了与传统互联网之间的对比和讨论。本文将深入探讨Web3与传统互联网之间的关系&#xff0c;挑战&#xff0c;以及可能的融…

舌头分割YOLOV8-SEG

舌头分割&#xff0c;基于YOLOV8-SEG&#xff0c;训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV的DNN调用&#xff0c;从而摆脱YOLO依赖&#xff0c;支持C,PYTHON,ANDROID开发 舌头分割YOLOV8-SEG