GNU/Linux下以C语言实现线程池


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。


intro

  • 本文代码附带详细注释
  • 本文主要讲述线程池实现的细节,其余不详细描述
  • 为保证精简,代码无错误检查,附带错误检查的详细代码见文章末尾

线程池的组成

  • 任务队列:存储需要处理的任务
  • 工作线程:处理任务 / n个
  • 管理者线程:管理工作线程 / 1个

任务队列

  • 以结构体方式定义
typedef struct Task
{
    void (*func)(void *arg);//指针函数:接受一个void*,无返回
    void *arg;              //具体任务
}Task;

工作线程与管理者线程

  • 构成线程池的主要部分

线程池结构体

struct ThreadPool
{
    //任务队列
    Task *taskQ;
    int queueCapacity;              //最大容量
    int queueSize;                  //当前任务个数
    int queueBegin;                 //队头 : 取数据
    int queueEnd;                   //队尾 : 放数据

    pthread_t managerID;            //管理者线程ID
    pthread_t *threadIDs;           //工作线程ID
    int minNum_Threads;              //最小线程 个数
    int maxNum_Threads;              //最大线程 个数
    int busyNum_Threads;             //忙线程 个数
    int liveNum_Threads;             //存活线程 个数
    int exitNum_Threads;             //要销毁的线程 个数

    pthread_mutex_t mutexPool;      //大锁:锁线程池
    pthread_mutex_t mutexBusy;      //小锁:锁忙线程个数(此变量最常用)
    pthread_cond_t Full;         //任务队列是否满
    pthread_cond_t Empty;        //任务队列是否空

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

线程池头文件(.h)

#ifndef _THREADPOOL_H
#define _THREADPOOL_H

#define NUM 2
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

typedef struct ThreadPool ThreadPool;

// 创建线程池并初始化
ThreadPool *ThreadPoolCreate(int min, int max, int queueSize);

// 工作线程任务函数
void *worker(void *arg);

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

// 销毁线程池
int threadPoolDestroy(ThreadPool *pool);

// 给线程池添加任务
void threadPoolAdd(ThreadPool *pool, void(*func)(void*), void*arg);

// 单个线程退出
void threadExit(ThreadPool* pool);

#endif // !_THREADPOOL_H

具体函数实现

  • 创建线程池并初始化
ThreadPool *ThreadPoolCreate(int min, int max, int queueSize)
//接受参数为 线程池最小线程数 线程池最大线程数 任务队列
{
    
    ThreadPool *pool = (ThreadPool *)malloc(sizeof(ThreadPool));//malloc一块连续地址
do{
    //线程
    pool->threadIDs = (pthread_t *)malloc(sizeof(pthread_t)*max);
    memset(pool->threadIDs, 0, sizeof(pthread_t)*max);
    pool->maxNum_Threads = max;//最大线程个数
    pool->minNum_Threads = min;//最小线程个数
    pool->liveNum_Threads = min;//存活的线程个数:相等于最小数
    pool->busyNum_Threads = 0;//忙的线程个数:现在还没有任务被读取
    pool->exitNum_Threads = 0;//需要推出的线程的个数

    //锁与条件变量
    pthread_mutex_init(&pool->mutexPool, NULL);
    pthread_mutex_init(&pool->mutexBusy, NULL);
    pthread_cond_init(&pool->Empty, NULL);
    pthread_cond_init(&pool->Full, NULL);

    //任务队列
    pool->taskQ = (Task*)malloc(sizeof(Task)*queueSize);

    pool->queueCapacity = queueSize;//队列最大容量
    pool->queueSize = 0;//队列当前任务个数
    pool->queueBegin = 0;//队头
    pool->queueEnd = 0;//队尾

    //线程池是是否需要销毁flag
    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);

//do...while中并无break是因为当前代码不附带错误检查,只提供框架,详细代码参见文章末尾

//销毁资源:只有当从从do...while中异常跳出才需要
    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);
        // 判断当前任务队列是否为空
        while(pool->queueSize == 0 && !pool->shutdown){
            pthread_cond_wait(&pool->Empty, &pool->mutexPool);//如果空或线程池需要销毁,则将线程阻塞于此处
            if(pool->exitNum_Threads > 0){//根据需要退出的线程的个数来决定需要自杀的线程个数
            //线程从此处往下执行就是去销毁自己
                pool->exitNum_Threads--;
                //再加一层判断,确保即使此线程自杀,存活的线程数仍大于要求的最小线程数
                if(pool->liveNum_Threads > pool->minNum_Threads){
                    pool->liveNum_Threads--;
                    pthread_mutex_unlock(&pool->mutexPool);
                    threadExit(pool);
                }
            }
        }

        // 判断线程池是否已关闭
        if(pool->shutdown == 1){
            pthread_mutex_unlock(&pool->mutexPool);
            threadExit(pool);
        }

        //如果任务队列不为空,线程池不需要销毁,那么工作线程就可以开始上班了

        //从任务队列取出一个任务
        Task task;
        task.func = pool->taskQ[pool->queueBegin].func;
        task.arg = pool->taskQ[pool->queueBegin].arg;

        // 移动头结点
        pool->queueBegin = (pool->queueBegin + 1) % pool->queueCapacity;//循环
        pool->queueSize--;

        //解锁
        pthread_cond_signal(&pool->Full);//通知阻塞在Full的线程开始生产
        pthread_mutex_unlock(&pool->mutexPool);

        printf("thread %ld start working\n",pthread_self());

        pthread_mutex_lock(&pool->mutexBusy);
        pool->busyNum_Threads++;//改变busy值
        pthread_mutex_unlock(&pool->mutexBusy);

        task.func(task.arg);//指针函数处理arg
        free(task.arg);
        task.arg = NULL;

        printf("thread %ld end working\n",pthread_self());
        pthread_mutex_lock(&pool->mutexBusy);
        pool->busyNum_Threads--;
        pthread_mutex_unlock(&pool->mutexBusy);
    }

    return NULL;
}
  • 管理者线程任务函数
void *manager(void * arg)
{
    ThreadPool *pool = (ThreadPool*)arg;
    while(pool->shutdown == 0){
        // 每3s检测一次
        sleep(3);
        
        // 取出线程池中任务的数量和当前线程的数量
        pthread_mutex_lock(&pool->mutexPool);
        int queueSize = pool->queueSize;
        int liveNum = pool->liveNum_Threads;
        pthread_mutex_unlock(&pool->mutexPool);

        // 取出忙线程个数
        pthread_mutex_lock(&pool->mutexBusy);
        int busyNum = pool->busyNum_Threads;
        pthread_mutex_unlock(&pool->mutexBusy);

        //添加线程(情况1)
        //任务个数 > 存活线程个数 && 存活线程个数 < 最大线程数
        if(queueSize > liveNum && liveNum < pool->maxNum_Threads){
            pthread_mutex_lock(&pool->mutexPool);
            int counter = 0;
            for(int i = 0; 
            i < pool->maxNum_Threads &&
            counter < NUM && 
            pool->liveNum_Threads < pool->maxNum_Threads;i++){
                if(pool->threadIDs[i] == 0){
                    pthread_create(&pool->threadIDs[i], NULL, worker, pool);
                    counter++;
                    pool->liveNum_Threads++;
                }
            }
            pthread_mutex_unlock(&pool->mutexPool);
        }

        // 销毁线程(情况2)
        // 忙的线程个数*2 < 存活线程数(此条逻辑取决于具体的业务需要) && 存活的线程 > 最小线程数
        if(busyNum*2 < liveNum && liveNum > pool->minNum_Threads){
            pthread_mutex_lock(&pool->mutexPool);
            pool->exitNum_Threads = NUM;
            pthread_mutex_unlock(&pool->mutexPool);
            //唤醒堵塞线程使其自杀
            for(int i = 0; i < NUM; i++){
                pthread_cond_signal(&pool->Empty);
            }
        }
    }
}
  • 销毁线程池
int threadPoolDestroy(ThreadPool *pool)
{
    // 关闭线程池
    pool->shutdown = 1;
    // 阻塞回收管理者线程
    pthread_join(pool->managerID, NULL);
    // 唤醒阻塞的消费者线程
    for(int i = 0; i < pool->liveNum_Threads; i++){
        pthread_cond_signal(&pool->Empty);//每次只能自杀一个,唤醒多了也没用
    }
    //释放malloc出来的堆内存
    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->Empty);
    pthread_cond_destroy(&pool->Full);

    free(pool);
    pool = NULL;

    return 0;
} 
  • 给线程池添加任务
void threadPoolAdd(ThreadPool * pool, void(*func)(void*), void*arg)
{
    pthread_mutex_lock(&pool->mutexPool);
    while(pool->queueSize == pool->queueCapacity &&
    pool->shutdown == 0){
        //队列满,阻塞生产者线程
        pthread_cond_wait(&pool->Full, &pool->mutexPool);
    }
    if(pool->shutdown == 1){
        //若需要销毁线程池
        pthread_mutex_unlock(&pool->mutexPool);
        return;
    }

    //添加任务
    pool->taskQ[pool->queueEnd].func = func;
    pool->taskQ[pool->queueEnd].arg = arg;
    pool->queueEnd = (pool->queueEnd+1) % pool->queueCapacity;//循环
    pool->queueSize++;

    pthread_cond_signal(&pool->Empty);//有任务了,唤醒因空队列而阻塞的线程
    pthread_mutex_unlock(&pool->mutexPool);
}
  • 单个线程退出
void threadExit(ThreadPool *pool)
{
    pthread_t tid = pthread_self();
    for(int i = 0; i < pool->maxNum_Threads; i++){
        if(pool->threadIDs[i] == tid){
            pool->threadIDs[i] = 0;
            printf("threadExit() called, %ld exiting\n", tid);
            break;
        }
    }

    pthread_exit(NULL);//退出当前线程
}

完整代码

  • 代码保存于我的GitHub仓库
  • 代码含有错误检查
  • 请在GUN/Linux环境下使用以下命令编译运行
gcc ThreadPool.c main.c -o ThreadPool -lpthread
./ThreadPool
  • -lpthread是为了链接线程库
  • main.c文件为测试demo

参考

  1. 《TLPI》
  2. 手写线程池 - C 语言版(爱编程的大丙)
  3. 大丙老师实在是太强了,关于线程池的更多细节可以去观看大丙老师的教学视频
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值