第四集 C/C++编程(线程池)

1.线程池的主要原理

当我们使用线程的时候会去创建一个线程,但是当并发的线程量很多的时候,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

线程池的作用就是:使得线程可以复用,执行完一个任务,并不被销毁,可以继续执行其他的任务

  • 线程池是一种多线程处理形式,处理过程中任务添加到队列,然后在创建线程后自动启动这些任务,线程池线程都是后台线程,每个线程都是用默认的优先级运行,并处于多线程单元中;
  • 如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池插入另一个辅助线程来使得所有处理器保持繁忙;
  • 如果所有线程池都保持繁忙,但队列中包含挂起的工作。则线程池将在一段时间后创建另一个辅助线程但线程池不会超过最大值,超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

原理:线程池分为3个部分,这三部分配合工作可以得到一个完整的线程池。

        1.任务队列:存储需要处理的任务,由工作的线程来处理这些任务。(看到任务队列就要想到生产者消费者模型,任务队列就相当于仓库或者车库,生产者不停放任务到仓库,消费者不断取任务。可以看前面几集的说法)第二集 C/C++多线程编程(奇迹行者还在更新)_昨夜惊蛰的博客-CSDN博客

  • 通过线程池提供的API接口,将一个待处理的任务添加到任务队列,或者从任务队列中删除。
  • 已处理的任务会从任务队列中删除
  • 向任务队列添加任务的一般是生产者,消费者来取出任务。

        2.工作线程(任务队列任务的消费者),N个,线程ID存储数组里

  • 线程中维护了一定数量的工作线程,作用是不停的读任务队列,从里面取任务并处理
  • 工作的线程相当于任务队列的消费者
  • 如果任务队列为空,工作的线程将会被阻塞(使用条件变量/信号量)
  • 如果阻塞后有了新的任务,由生产者将阻塞解除,工作线程开始工作

        3.管理者线程(不处理任务队列中的任务),1个,直接存储到一个pthread_t的变量里

  • 它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测。
  • 当任务过多的时候,可以适当创建一些新的线程。
  • 当任务过少的时候,可以适当销毁一些工作的线程。
  • 设置一个频率进行周期性检测(sleep)

生产者不在线程池内,是在外部进行添加任务的。是线程池的使用者!

2.构建一个线程池(C语言)

首先我们需要用C语言来编写一个线程池。主要采用数组或者链表存储任务队列里面的顺序。而C++则是采用容器,目前笔者还没有咋学C++,等之后学好了C++再进行线程池的书写。

2.1分析结构体

首先我们进行分析,对于我们需要创建两个结构体,一个是任务结构体,负责给与任务的具体内容,即每一个消费者线程需要干什么。然后创建一个线程池,记住线程池的三部分:任务队列,工作线程以及管理者线程,并在结构体中定义相关的参数。

/*任务结构体*/
typedef struct task
{
    void(*function)(void* arg);/*不知道要不要参数,所以用一个范型*/
    void* arg;
}TASK;

/*线程池结构体*/
typedef struct ThreadPool
{
    TASK* takeQ;       /*任务队列*/
    int queueCapacity;  /*容量*/
    int queueSize;   /*当前任务个数*/
    int queueFront;/*队头,用于取数据*/
    int queueRear; /*队尾,用于放数据,即先进先出*/


    pthread_t managerID;  /*管理者线程ID*/
    pthread_t *threadIDs;  /*工作的线程ID,有多个,所以直接指针定义数组,到时候直接指向数组地址即可,定义数组往往要指针*/
    int minNum;/*最少工作线程*/
    int maxNum;/*最多工程线程*/
    int busyNum;/*在工作中的线程个数,会一直变化,可以单独给他加个锁*/
    int liveNum;/*存活的线程,即目前有几个线程,算上工作和没在工作的*/
    int exitNum;/*要杀死的线程,即没事儿干的线程*/

    pthread_mutex_t mutexpool; /*锁整个线程池*/
    pthread_mutex_t mutexBusy;/*所busyNum变量*/

    pthread_cond_t notFull;    /*任务队列是不是满了*/
    pthread_cond_t notEmpty;/*任务队列是不是空了*/
    
    int shutdown; /*是否要销毁线程池,销毁为1,不销毁为0*/
}THREADPOOL;

2.2初始化函数

首先是创建线程池以及任务队列并需要进行初始化,其中使用do while(0),是为了在出错后可以先释放资源,再退出函数(因为可以使用break退出循环),具体注释都在代码之中。

/*返回值是指针类型,可以把里面的值传出来用,函数里面定义的东西可以影响到外面*/
THREADPOOL *threadPoolCreat(int min, int max, int queueSize)/*第三个参数任务队列容量*/
{
    THREADPOOL* pool = (THREADPOOL*)malloc(sizeof(THREADPOOL));
    /*使用do while(0)是为了,可以在出现问题时使用break跳出循环,并且可以释放资源,如果是有错误直接return则不会有释放资源这一步骤*/
    do
    {
        if(NULL == pool)
        {
            printf("(%s)分配内存失败!",__func__);
            break;
        }
        /*对内存空间进行一个初始化,用0进行初始化*/
        /*这里threadIDs是数组,每个位是一个pthread_t,所以是sizeof(pthread_t) * max*/
        memset(pool->threadIDs, 0, sizeof(pthread_t) * max);/*pthread_t是无符号长整形和int都是4个字节*/
        pool->minNum = min;
        pool->maxNum = max;
        pool->busyNum = 0;
        pool->liveNum = min; /*和最小个数相等,活着不一定在工作*/
        pool->exitNum = 0;
        /*初始化,成功时会为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("(%s)初始化失败",__func__);
            break;       
        }
    
        /*任务队列*/
        pool->taskQ = (TASK *)malloc(sizeof(TASK) * queueSize);
        if(NULL == pool->taskQ)
        {
            printf("(%s)分配内存失败!",__func__);
            break;
        }
        pool->queueCapacity = queueSize;/*任务队列容量,即容得下多少个任务*/
        pool->queueSize = 0;/*当前工作数量为0*/
        pool->queueFront = 0;
        pool->queueRear = 0;
        pool->shutdown = 0;/*0表示不销毁*/


    /*创建线程,一类是管理者(监事者)线程,一类是工作线程*/
    pthread_create(&pool->managerID, NULL, manager, NULL);
    
        for(int i = 0; i < min; ++i)/*按最少的工作线程数量进行创建,最少也得有这几个在工作,除非都结束工作,只要一开始就要至少这么多*/
        {
        /*取数组的地址进行创建,这里记得是加&*/
            pthread_create(&pool->threadIDs[i], NULL, worker, pool);
        }  
        return pool;/*返回线程池的地址*/
    } while (0);
    
    /*资源释放*/
    if(pool->threadIDs) 
    {
        free(pool->threadIDs);
    }
    if(pool->taskQ)
    {
        free(pool->taskQ);
    }
    if(pool)
    {
        free(pool);
    }

    return NULL;
}

2.3消费者线程函数

这个线程是用来取出任务队列中的任务,从头开始取,然后令头部右移,即下一个线程来取的时候取得是第二个,取出来进行处理的任务并不需要加锁因为互不影响,因此并不是说按顺序输出的100至199。所以记得对线程池进行操作(取任务)的时候,需要加锁,不然会造成混乱;之后正在工作的队列会加一,这个运算也需要加我们特定的锁,专门锁忙碌队列数量的,目的是防止在后面管理者线程根据工作队列数量和活着队列数量进行比对时,不会出错。如果不加锁,各个线程可能还没进行工作,就率先加一,会造成这个工作的线程数量混乱。

void* worker(void *arg)
{
    THREADPOOL *pool = (THREADPOOL*)arg;
    while(1)
    {
        pthread_mutex_lock(&pool->mutexpool);
        /*当前任务队列是否为空*/
        /*当任务个数为0,即没有货物的时候,且线程池没有关闭(!pool->shutdown)*/
        /*如果shutdown为0表示没销毁,!之后为1可以继续运行;反之shutdown为1指被销毁,!之后为0无法继续运行*/
        while(pool->queueSize = 0 && !pool->shutdown)
        {
            /*阻塞工作线程*/
            pthread_cond_wait(&pool->notEmpty, &pool->mutexpool);/*不要被名字误导,这里指不是空就唤醒*/
        }
        
        /*如果线程池被关闭了*/
        if(pool->shutdown)
        {
            pthread_mutex_unlock(&pool->mutexpool);/*先打开防止死锁*/
            pthread_exit(NULL);/*当前线程退出*/
        }


        /*开始工作,从任务队列中取出一个任务*/
        TASK task;
        /*先用任务结构体定义一个任务,这个任务等于从线程池的任务列表中取出的任务*/
        task.function = pool->taskQ[pool->queueFront].function;/*这个任务队列是一个数组,从头部取数据*/
        /*反正记得数组要用指针来定义!*/
        task.arg = pool->taskQ[pool->queueFront].arg;
        /*移动头节点,形成一个环形遍历,比如容量为4,头节点从0到3,然后又到0*/
        pool->queueFront = (pool->queueFront + 1 ) % pool->queueCapacity;/*比如第一个就是从0移动到了1*/
        pool->queueSize--;/*当前任务个数被减少一个*/

        pthread_mutex_unlock(&pool->mutexpool);
        /*busyNum是一个共享的资源也是需要加单独的那个锁的。共同的资源防止混乱!
        因为后面有需要判断要不要销毁线程的函数,这边不能乱*/
        pthread_mutex_lock(&pool->busyNum);
        pool->busyNum++;
        pthread_mutex_unlock(&pool->busyNum);

        task.function(task.arg);/*执行函数*/
        free(task.arg);
        task.arg = NULL;
        printf("thread %ld end working……\n");

        pthread_mutex_lock(&pool->busyNum);/*执行完毕后忙的线程就减1*/
        pool->busyNum--;
        pthread_mutex_unlock(&pool->busyNum);        
    }
}

2.4管理者线程函数

这个线程函数主要负责的应该是监测目前或者的线程数以及正在工作中的线程数,当任务量过少的时候,会销毁一些不在工作的线程,如果任务量过多的时候,会添加一些额外的线程进行工作。中间还要进行判断,当线程池关闭的时候,生产者同样不应该继续生产任务。

在创建和初始化函数中,首先创建了管理者线程,然后创建了最小数量的消费者线程。线程是在循环中无限复用的。残忍的真相:如果管理者发现任务太少了,线程过多,他就会引导这些线程去自杀,退出线程。如果任务过多,它就会创建新的线程来继续执行。拥有一定的监视周期。

void* manager(void *arg)
{
    THREADPOOL *pool = (THREADPOOL*)arg;
    while (!pool->shutdown)
    {
        /*检测周期*/
        sleep(3);

        /*取出线程中任务数量和当前线程数量*/
        pthread_mutex_lock(&pool->mutexpool);
        int queueSize = pool->queueSize; /*获取当前任务个数*/
        int liveNum = pool->liveNum;/*活着的线程数*/
        pthread_mutex_unlock(&pool->mutexpool);

        /*也可以把busyNum放到上面取单独锁一个变量比锁一个线程池效率高*/
        pthread_mutex_lock(&pool->mutexBusy);
        int busyNum = pool->busyNum; /*在工作的线程数*/
        pthread_mutex_unlock(&pool->mutexBusy);

        /*添加线程*/
        /*任务个数 > 存活的线程数 && 存活的线程数 < 最大线程数*/
        if(queueSize > liveNum && liveNum < pool->maxNum)
        {
            int counter = 0;/*counter加到2就不可以了*/
            pthread_mutex_lock(&pool->mutexpool);/*对数据操作需要加锁*/
            /*不能加超过NUMBER个线程,同时需要看活着的线程数不能超过最大工作线程*/
            for(int i = 0; i < pool->maxNum && counter < NUMBER && pool->liveNum < pool->maxNum; ++i)
            {
                /*从线程ID找一个空闲的位置*/
                if(pool->threadIDs[i] == 0)
                {
                    pthread_create(&pool->threadIDs[i], NULL, worker, pool);
                    counter++;
                    pool->liveNum++;
                }        
            }
            pthread_mutex_unlock(&pool->mutexpool);
        }

        /*销毁线程*/
        /*工作的线程 * 2 < 存活的线程数 && 存活的线程数 > 最小线程数*/
        if(busyNum * 2 < liveNum && liveNum > pool->minNum)
        {
            pthread_mutex_lock(&pool->mutexpool);
            pool->exitNum = NUMBER;/*一次性销毁两个*/
            
            /*让工作的线程自杀,引导自杀*/
            for(int i = 0; i < NUMBER; ++i)
            {
    /*唤醒后,这些存活着但是没有事情干的队列(因为任务列表被取完了)
    照理说唤醒后,还会继续卡在上面,因为任务队列里没有任务了,但是我们可以增加判断是否要销毁*/
                pthread_cond_signal(&pool->notEmpty);/*唤醒两次*/
            }
            pthread_mutex_unlock(&pool->mutexpool);
        }
    }
    return NULL;
}

2.5线程退出函数

void threadExit(THREADPOOL* pool)
{
    pthread_t tid = pthread_self();/*threadID是包含所有消费者线程的,不管在不在工作(有的是工作结束停在那里)*/
    for(int i = 0; i < pool->maxNum; ++i)
    {
        if(pool->threadIDs[i] == tid)
        {
            pool->threadIDs[i] = 0;
            printf("threadExit() called,%ld exiting……\n", tid);
            break;
        }
    }
    pthread_exit(NULL);
}

2.6线程销毁函数

int threadPoolDestroy(THREADPOOL* pool)
{
    if(pool == NULL)
    {
        return -1;
    }
    /*关闭线程池*/
    pool->shutdown = 1;/*所有线程都是对同一个线程池进行工作的*/
    /*阻塞回收管理者线程*/
    pthread_join(pool->managerID, NULL);
    /*唤醒阻塞的消费者*/
    for(int i = 0; i < pool->liveNum; ++i)
    {
        pthread_cond_signal(&pool->notEmpty);/*唤醒后就会被销毁,因为解除阻塞后会判断线程池是否关闭*/
    }
    if(pool -> taskQ)
    {
        free(pool->taskQ);
    }
    if(pool->threadIDs)
    {
        free(pool->threadIDs);
    }
    pthread_mutex_destroy(&pool->mutexpool);
    pthread_mutex_destroy(&pool->mutexBusy);
    pthread_cond_destroy(&pool->notEmpty);
    pthread_cond_destroy(&pool->notFull);
    free(pool);/*释放之前关闭互斥锁*/
    pool = NULL;
    return 0;
}

2.7代码整体展示:

threadpool.h

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/*任务结构体*/
typedef struct task
{
    void(*function)(void* arg);/*不知道要不要参数,所以用一个范型*/
    void* arg;
}TASK;

/*线程池结构体*/
typedef struct ThreadPool
{
    TASK* taskQ;       /*任务队列*/
    int queueCapacity;  /*任务队列的容量*/
    int queueSize;   /*当前任务个数*/
    int queueFront;/*队头,用于取数据*/
    int queueRear; /*队尾,用于放数据,即先进先出*/


    pthread_t managerID;  /*管理者线程ID*/
    pthread_t *threadIDs;  /*消费者的线程ID,有多个,所以直接指针定义数组,到时候直接指向数组地址即可,定义数组往往要指针*/
    int minNum;/*最少工作线程*/
    int maxNum;/*最多工作线程*/
    int busyNum;/*在工作中的线程个数,会一直变化,可以单独给他加个锁*/
    int liveNum;/*存活的线程,即目前有几个线程,算上工作和没在工作的*/
    int exitNum;/*要杀死的线程,即没事儿干的线程*/

    pthread_mutex_t mutexpool; /*锁整个线程池*/
    pthread_mutex_t mutexBusy;/*所busyNum变量*/

    pthread_cond_t notFull;    /*任务队列是不是满了*/
    pthread_cond_t notEmpty;/*任务队列是不是空了*/

    int shutdown; /*是否要销毁线程池,销毁为1,不销毁为0*/
}THREADPOOL;

/*创建线程池函数并初始化,返回的是一个线程池类型的地址!这样每个都能调用,不需要一个个复制过去*/
/*学过指针你会发现,如果函数返回的不是地址,调用函数后,不会改变主函数中定义的变量,很有问题*/
THREADPOOL *threadPoolCreat(int min, int max, int queueSize);

/*销毁线程池函数*/
int threadPoolDestroy(THREADPOOL* pool);

/*添加任务函数*/
void threadPoolAdd(THREADPOOL* pool, void(*function)(void* arg), void* arg);

/*获取线程池工作的线程个数*/
int threadpoolBusyNum(THREADPOOL* pool);

/*获取活着的线程个数*/
int threadpoolAliveNum(THREADPOOL* pool);
/*消费者线程函数*/
void* worker(void *arg);

/*管理者线程函数*/
void* manager(void *arg);

/*当前线程退出时,将threadIDs对应元素值修改为0,使其可以运用*/
void threadExit(THREADPOOL* pool);

/*测试工作函数*/
void taskFunc(void* arg);

threadpool.c

#include "threadpool.h"
#define NUMBER (2)  /*添加/删除线程两个*/


/*返回值是指针类型,可以把里面的值传出来用,函数里面定义的东西可以影响到外面*/
THREADPOOL *threadPoolCreat(int min, int max, int queueSize)/*第三个参数任务队列容量*/
{
    /*结构体给内存*/
    THREADPOOL* pool = (THREADPOOL*)malloc(sizeof(THREADPOOL));
    /*使用do while(0)是为了,可以在出现问题时使用break跳出循环,并且可以释放资源,如果是有错误直接return则不会有释放资源这一步骤*/
    do
    {
        if(NULL == pool)
        {
            printf("(%s)分配内存失败!",__func__);
            break;
        }
        /*对内存空间进行一个初始化,只要是数组都需要用malloc来分配空间,用0进行初始化*/
        /*这里threadIDs是数组,每个位是一个pthread_t,所以是sizeof(pthread_t) * max*/
        pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * max);
        if(NULL == pool->threadIDs)
        {
            printf("(%s)分配内存失败!",__func__);
            break;           
        }
        memset(pool->threadIDs, 0, sizeof(pthread_t) * max);/*pthread_t是无符号长整形和int都是4个字节*/
        pool->minNum = min;
        pool->maxNum = max;
        pool->busyNum = 0;
        pool->liveNum = min; /*和最小个数相等,活着不一定在工作*/
        pool->exitNum = 0;
        /*初始化,成功时会为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("(%s)初始化失败",__func__);
            break;       
        }
    
        /*任务队列*/
        pool->taskQ = (TASK *)malloc(sizeof(TASK) * queueSize);
        if(NULL == pool->taskQ)
        {
            printf("(%s)分配内存失败!",__func__);
            break;
        }
        pool->queueCapacity = queueSize;/*任务队列容量,即容得下多少个任务*/
        pool->queueSize = 0;/*当前工作数量为0*/
        pool->queueFront = 0;
        pool->queueRear = 0;
        pool->shutdown = 0;/*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;
}

void* worker(void *arg)
{
    THREADPOOL *pool = (THREADPOOL*)arg;

    while(1)
    {
        pthread_mutex_lock(&pool->mutexpool);
        /*当前任务队列是否为空*/
        /*当任务个数为0,即没有货物的时候,且线程池没有关闭(!pool->shutdown)*/
        /*如果shutdown为0表示没销毁,!之后为1可以继续运行;反之shutdown为1指被销毁,!之后为0无法继续运行*/
        while(pool->queueSize == 0 && !pool->shutdown)
        {
            /*阻塞工作线程*/
            pthread_cond_wait(&pool->notEmpty, &pool->mutexpool);/*不要被名字误导,这里指“不是空”就唤醒*/
            /*正常没有活了就被阻塞在这里,但是阻塞的太多了,我们需要踢掉一部分*/
            /*当多了很多不干活的线程时,我们把他们唤醒并判断是不是要销毁*/
            if(pool->exitNum > 0)
            {
                pool->exitNum--;
                if(pool->liveNum > pool->minNum)/*再次进行判断目前存活的数量是不是大于最小工作线程*/
                {
                    pool->liveNum--;/*存活的线程数需要-1*/
                    pthread_mutex_unlock(&pool->mutexpool);/*退出前记得一定要解锁,防止死锁,因为条件变量唤醒后会自动加锁*/
                    threadExit(pool);/*退出当前线程,唤醒两次并且正好要退两个线程*/
                }
            }
        }
        /*如果线程池被关闭了*/
        if(pool->shutdown)
        {
            pthread_mutex_unlock(&pool->mutexpool);/*先打开防止死锁*/
            threadExit(pool);/*当前线程退出*/
        }

        /*开始工作,从任务队列中取出一个任务*/
        TASK task;
        /*先用任务结构体定义一个任务,这个任务等于从线程池的任务列表中取出的任务*/
        task.function = pool->taskQ[pool->queueFront].function;/*这个任务队列是一个数组,从头部取数据*/
        /*反正记得数组要用指针来定义!*/
        task.arg = pool->taskQ[pool->queueFront].arg;
        /*移动头节点,形成一个环形遍历,比如容量为4,头节点从0到3,然后又到0*/
        pool->queueFront = (pool->queueFront + 1 ) % pool->queueCapacity;/*比如第一个就是从0移动到了1*/
        pool->queueSize--;/*当前任务个数被减少一个*/

        pthread_cond_signal(&pool->notFull);/*消费后唤醒生产者*/
        pthread_mutex_unlock(&pool->mutexpool);
        printf("thread %ld start working…\n",pthread_self());
        /*busyNum是一个共享的资源也是需要加单独的那个锁的。共同的资源防止混乱!
        因为后面有需要判断要不要销毁线程的函数,这边不能乱*/
        pthread_mutex_lock(&pool->mutexBusy);
        pool->busyNum++;
        pthread_mutex_unlock(&pool->mutexBusy);

        task.function(task.arg);/*执行函数*/
        free(task.arg);
        task.arg = NULL;
        printf("thread %ld end working……\n",pthread_self());
        pthread_mutex_lock(&pool->mutexBusy);/*执行完毕后忙的线程就减1,但是活线程数不变*/
        pool->busyNum--;
        pthread_mutex_unlock(&pool->mutexBusy);        
    }
    return NULL;
}

void* manager(void *arg)
{
    THREADPOOL *pool = (THREADPOOL*)arg;
    while (!pool->shutdown)
    {
        /*检测周期*/
        sleep(3);

        /*取出线程中任务数量和当前线程数量*/
        pthread_mutex_lock(&pool->mutexpool);
        int queueSize = pool->queueSize; /*获取当前任务个数*/
        int liveNum = pool->liveNum;/*活着的线程数*/
        pthread_mutex_unlock(&pool->mutexpool);

        /*也可以把busyNum放到上面取单独锁一个变量比锁一个线程池效率高*/
        pthread_mutex_lock(&pool->mutexBusy);
        int busyNum = pool->busyNum; /*在工作的线程数*/
        pthread_mutex_unlock(&pool->mutexBusy);

        /*添加线程*/
        /*任务个数 > 存活的线程数 && 存活的线程数 < 最大线程数*/
        if(queueSize > liveNum && liveNum < pool->maxNum)
        {
            int counter = 0;/*counter加到2就不可以了*/
            pthread_mutex_lock(&pool->mutexpool);/*对数据操作需要加锁*/
            /*不能加超过NUMBER个线程,同时需要看活着的线程数不能超过最大工作线程*/
            for(int i = 0; i < pool->maxNum && counter < NUMBER && pool->liveNum < pool->maxNum; ++i)
            {
                /*从线程ID找一个空闲的位置*/
                if(pool->threadIDs[i] == 0)
                {
                    pthread_create(&pool->threadIDs[i], NULL, worker, pool);
                    counter++;
                    pool->liveNum++;
                }        
            }
            pthread_mutex_unlock(&pool->mutexpool);
        }

        /*销毁线程*/
        /*工作的线程 * 2 < 存活的线程数 && 存活的线程数 > 最小线程数*/
        if(busyNum * 2 < liveNum && liveNum > pool->minNum)
        {
            pthread_mutex_lock(&pool->mutexpool);
            pool->exitNum = NUMBER;/*一次性销毁两个*/
            
            /*让工作的线程自杀,引导自杀*/
            for(int i = 0; i < NUMBER; ++i)
            {
    /*唤醒后,这些存活着但是没有事情干的队列(因为任务列表被取完了)
    照理说唤醒后,还会继续卡在上面,因为任务队列里没有任务了,但是我们可以增加判断是否要销毁*/
                pthread_cond_signal(&pool->notEmpty);/*唤醒两次*/
            }
            pthread_mutex_unlock(&pool->mutexpool);
        }
    }
    return NULL;
}

void threadExit(THREADPOOL* pool)
{
    pthread_t tid = pthread_self();/*threadID是包含所有消费者线程的,不管在不在工作(有的是工作结束停在那里)*/
    for(int i = 0; i < pool->maxNum; ++i)
    {
        if(pool->threadIDs[i] == tid)
        {
            pool->threadIDs[i] = 0;
            printf("threadExit() called,%ld exiting……\n", tid);
            break;
        }
    }
    pthread_exit(NULL);
}
/*生产者*/
void threadPoolAdd(THREADPOOL* pool, void(*func)(void*), void* arg)
{
    pthread_mutex_lock(&pool->mutexpool);
    /*线程池还能使用但是任务队列满了,所以生产者阻塞*/
    while(pool->queueSize == pool->queueCapacity && !pool->shutdown)
    {
        pthread_cond_wait(&pool->notFull, &pool->mutexpool);
    }
    /*销毁说明也不再生产工作*/
    if (pool->shutdown)
    {
        pthread_mutex_unlock(&pool->mutexpool);
        return; /*直接返回不再继续*/
    }
    /*添加任务*/
    pool->taskQ[pool->queueRear].function = func;
    pool->taskQ[pool->queueRear].arg = arg;
    pool->queueRear = (pool->queueRear + 1) % pool->queueCapacity;/*尾巴后移*/
    pool->queueSize++;

    pthread_cond_signal(&pool->notEmpty);/*有任务就会去唤醒阻塞的消费者*/
    pthread_mutex_unlock(&pool->mutexpool);
}

int threadpoolBusyNum(THREADPOOL* pool)
{
    pthread_mutex_lock(&pool->mutexBusy);
    int busyNum = pool->busyNum;
    pthread_mutex_unlock(&pool->mutexBusy);
    return busyNum;
}

int threadpoolAliveNum(THREADPOOL* pool)
{
    pthread_mutex_lock(&pool->mutexpool);
    int aliveNum = pool->liveNum;
    pthread_mutex_unlock(&pool->mutexpool);
    return aliveNum;
}

int threadPoolDestroy(THREADPOOL* pool)
{
    if(pool == NULL)
    {
        return -1;
    }
    /*关闭线程池*/
    pool->shutdown = 1;/*所有线程都是对同一个线程池进行工作的*/
    /*阻塞回收管理者线程*/
    pthread_join(pool->managerID, NULL);
    /*唤醒阻塞的消费者*/
    for(int i = 0; i < pool->liveNum; ++i)
    {
        pthread_cond_signal(&pool->notEmpty);/*唤醒后就会被销毁,因为解除阻塞后会判断线程池是否关闭*/
    }
    if(pool -> taskQ)
    {
        free(pool->taskQ);
    }
    if(pool->threadIDs)
    {
        free(pool->threadIDs);
    }
    pthread_mutex_destroy(&pool->mutexpool);
    pthread_mutex_destroy(&pool->mutexBusy);
    pthread_cond_destroy(&pool->notEmpty);
    pthread_cond_destroy(&pool->notFull);
    free(pool);/*释放之前关闭互斥锁*/
    pool = NULL;
    return 0;
}

void taskFunc(void* arg)/*传递堆内存*/
{
    int num = *(int*)arg;/*先转成整形,再取数据*/
    printf("thread is working, number = %d, tid = %ld\n",num,pthread_self() );
    sleep(1);
}
int main()
{
    /*创建出一个线程池*/
    THREADPOOL* pool =  threadPoolCreat(3, 10 ,100);/*min,max,最大容量*/
    for(int i = 0; i < 100; ++i)
    {
        int* num = (int*)malloc(sizeof(int));
        *num = i + 100;
        threadPoolAdd(pool, taskFunc, num);/*把num传递给函数*/
    }
    sleep(35);
    threadPoolDestroy(pool);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值