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

devtools/2024/10/21 6:02:16/

文章目录

  • 定义线程池结构
    • 任务结构体
    • 定义线程池结构体
  • 组织头文件
  • 创建线程池实例
    • 函数原型定义
    • 线程池创建函数实现
      • 初始化线程池结构体指针
      • 初始化线程池结构体的各类参数
  • 定义线程池的结构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/devtools/27463.html

相关文章

vue3 引用虚拟键盘simple-keyboard

simple-keyboard官网地址&#xff1a;https://virtual-keyboard.js.org 目前实现效果图是&#xff08;实现数字、大小写字母键盘&#xff09;&#xff1a; 1.需要先安装simple-keyboard npm install simple-keyboard --save2.封装sinpleKeyboard 组件 <!-- keyboard-bo…

k8s pod 镜像拉取策略

在 Kubernetes (k8s) 中&#xff0c;Pod 容器镜像的拉取策略通过 imagePullPolicy 属性来控制。这一策略决定了 kubelet 如何以及何时从容器镜像仓库中拉取镜像。以下是三种主要的镜像拉取策略及其详细说明&#xff1a; Always: 说明: 这是默认的拉取策略。当设置为 Always 时&…

第72天:漏洞发现-Web框架中间件联动GobyAfrogXrayAwvsVulmap

案例一&#xff1a;某 APP-Web 扫描-常规&联动-Burp&Awvs&Xray Acunetix 一款商业的 Web 漏洞扫描程序&#xff0c;它可以检查 Web 应用程序中的漏洞&#xff0c;如 SQL 注入、跨站脚本攻击、身份验证页上的弱口令长度等。它拥有一个操作方便的图形用户界 面&#…

解锁图像新维度:剑桥联手英特尔,利用大语言模型重构逆向图形学!

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享&#xff0c;与你一起了解前沿深度学习信息&#xff01; 引言&#xff1a;探索逆图形学的新视角 逆图形学&#xff08;Inverse Graphics&#xff09;是计算机视觉和图形学中的一个基本挑战&#xff0c;它涉及将图像…

LCR 150. 彩灯装饰记录 II

1.leetcode原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 2.题目描述 一棵圣诞树记作根节点为 root 的二叉树&#xff0c;节点值为该位置装饰彩灯的颜色编号。请按照从左到右的顺序返回每一层彩灯编号&#xff0c;每一层的结果记录于一行。 示例 1&#xff…

Python和Julia河流湖泊沿海水域特征数值算法模型

&#x1f3af;要点 一维水流场景计算和绘图&#xff1a; &#x1f3af;恒定透射率水头和流量计算&#xff1a;&#x1f58a;两条完全穿透畜水层理想河流之间 | &#x1f58a;无承压畜水层两侧及两条完全穿透畜水层的补给 | &#x1f58a;分水岭或渗透性非常低的岩体的不渗透边…

[华为OD]实现一个支持优先级的队列,高优先级先出队列 100

题目&#xff1a; 实现一个支持优先级的队列&#xff0c;高优先级先出队列&#xff1b;同优先级时先进先出。 如果两个输入数据和优先级都相同&#xff0c;则后一个数据不入队列被丢弃。 队列存储的数据内容是一个整数。 输入描述&#xff1a; 一组待存入队列的数据&#…

【ARM 裸机】BSP 工程管理

回顾一下上一节&#xff1a;【ARM 裸机】NXP 官方 SDK 使用&#xff0c;我们发现工程文件夹里面各种文件非常凌乱&#xff1b; 那么为了模块化整理代码&#xff0c;使得同一个属性的文件存放在同一个目录里面&#xff0c;所以学习 BSP 工程管理非常有必要。 1、准备工作 新建…