C/C++ 线程池的作用与结构 & C++代码实现

线程池的作用

如果多次使用线程,那么就需要多次的创建并撤销线程。但是创建/撤销的过程会消耗资源。线程池是一种数据结构,其中维护着多个线程,这避免了在处理短时间任务时,创建与销毁线程的代价。即在程序开始运行前预先创建一定数量的线程放入空闲队列中,这些线程都是处于阻塞状态,基本不消耗CPU,只占用较小的内存空间,程序在运行时,只需要从线程池中拿来用就可以了,大大提高了程序运行效率。

线程池结构示意图(C++)

在这里插入图片描述

C++代码

代码结构:

  • myTaskQueue.hpp:声明了任务类myTask和任务队列类myTaskQueue
  • myTaskQueue.cpp:实现了任务类myTask和任务队列类myTaskQueue
  • myThreadPool.hpp:声明了线程池类myThreadPool
  • myThreadPool.cpp:实现了线程池类myThreadPool
  • test.cpp:main函数测试

myTaskQueue.hpp

#include <stdio.h>
#include <mutex> //锁类
#include <queue>
using namespace std;
using callback=void (*) (void* args);
typedef class myTask{  //任务
public:
    callback function; // 任务函数 void function(void* arg)类型的指针
    void* args; // 任务函数的参数
    myTask(){  //无参构造函数
        function=nullptr;
        args=nullptr;
    }
    myTask(callback function, void *args){ //有参构造函数
        this->function = function;
        this->args = args;
    }
    ~myTask(){}  //析构函数
}task;

class myTaskQueue{  // 任务队列
private:
    queue<task>q;  //任务队列
    mutex m; //任务队列的互斥锁
    
public:
    myTaskQueue(); //无参构造
    void add(task t); //添加任务
    task get(); //取出任务
    bool is_empty(); //判断是否为空
    int get_length(); //返回队列中任务数量
};

myTaskQueue.cpp

#include "myTaskQueue.hpp"
using namespace std;

myTaskQueue::myTaskQueue(){
    //空构造
}

void myTaskQueue::add(task t){
    lock_guard<mutex> lg(m); //上锁
    q.push(t);
}

task myTaskQueue::get(){
    lock_guard<mutex> lg(m); //上锁
    task ret = q.front();
    q.pop();
    return ret;
}

bool myTaskQueue::is_empty(){
    lock_guard<mutex> lg(m); //上锁
    return q.empty();
}

int myTaskQueue::get_length(){
    lock_guard<mutex> lg(m); //上锁
    return (int)q.size();
}

myThreadPool.hpp

#include <stdio.h>
#include <thread> //线程类
#include <condition_variable> //条件变量锁类
#include <mutex> //互斥锁类
#include "myTaskQueue.hpp"  //任务队列类
using namespace std;
typedef class myThreadPool{ // 线程池类
private:
    //任务队列相关
    myTaskQueue taskQ;
    //工作线程相关
    thread** worker; //工作线程的二级指针(thread*类型的数组)
    int minNum; //最小线程数
    int maxNum; //最大线程数
    int liveNum; //存活的线程数
    int busyNum; //忙碌的线程数
    //管理线程相关
    thread *manager; //管理线程
    int deleteNum; //要删除的线程数
    int shutdown; //判断线程池是否关闭
    //锁相关
    mutex m; //临界资源互斥锁
    condition_variable_any condEmpty;  //条件锁,判断任务队列是否为空
    static void* work(void* args); //工作线程函数
    static void* manage(void* args); //管理线程函数
    
public:
    myThreadPool(int minNum=3, int maxNum=10); //构造函数
    ~myThreadPool(); //析构函数
    void add_task(task t); //添加任务
    int get_live_thread_num(); //查看存活的线程数量
    int get_busy_thread_num(); //查看忙碌的线程数量
    int get_task_num(); //查看队列中任务的数量
    void thread_exit(); //线程退出函数
    
}threadPool;

myThreadPool.cpp

#include "myThreadPool.hpp"
#include <unistd.h>
#include <iostream>
using namespace std;
using callback=void (*)(void* args);

myThreadPool::myThreadPool(int minNum, int maxNum){
    
    //初始化工作线程
    this->worker = new thread* [maxNum]; //初始化10个thread*类型
    for(int i=0;i<maxNum;++i)worker[i]=nullptr; //赋值为nullptr
    for(int i=0;i<minNum;++i){  //初始化minNum个线程
        worker[i] = new thread(work, this); //这里需要传递this,因为work和manage都是静态成员函数,不能直接访问类的普通成员
    }
    this->minNum = minNum;
    this->maxNum = maxNum;
    this->liveNum = minNum;
    this->busyNum = 0;
    
    // 初始化管理线程
    manager = new thread(manage, this); //这里需要传递this,因为work和manage都是静态成员函数,不能直接访问类的普通成员
    deleteNum = 0;
    shutdown = 0;
    
    // 初始化锁
    // 无需初始化
    
    cout<<"线程池创建成功"<<endl;
}

// 析构函数
myThreadPool::~myThreadPool(){
    shutdown=1;
    this->manager->join(); //等待管理线程的终止
    // 唤醒所有的worker线程
    for(int i=0;i<this->maxNum;++i){
        this->condEmpty.notify_all();
    }
    //阻塞等待所有worker线程退出
    for(int i=0;i<this->maxNum;++i){
        if(worker[i]!=nullptr)worker[i]->join();
    }

    if(this->worker)delete worker;
    if(this->manager)delete manager;
    
    cout<<"线程池删除成功"<<endl;
}

//每次从任务队列中取出一个任务并执行
void* myThreadPool::work(void* args){  //这里arg是一个pool的this指针
    //拿到对象的指针
    myThreadPool* pool = (myThreadPool*)args;
    
    // 循环执行
    while(1){
        
        unique_lock<mutex> ulock(pool->m); //静态成员函数可以访问类的私有成员,访问临界资源,上互斥锁,lock_guard会自动释放锁
        while(!pool->shutdown && pool->taskQ.get_length()==0){ //如果没有任务,则需要阻塞等待。注意这里不能直接pool->get_task_num(),因为该函数内部也需要互斥锁pool->m,会出现死锁
            pool->condEmpty.wait(ulock); //等待有任务
            if(pool->deleteNum>0){
                pool->deleteNum--;
                pool->liveNum--;
                pool->thread_exit();
                return nullptr;
            }
        }
        if(pool->shutdown){  //如果要关闭线程池,则销毁当前线程
            pool->thread_exit();
            return nullptr; //需要返回nullptr让线程走完之后自动销毁
        }
        task t = pool->taskQ.get(); // 拿到任务
        pool->busyNum++; //忙碌的线程+1
        ulock.unlock(); //解锁,上面有一些退出的位置不需要手动unlock,因为unique_lock会自动unlock
        
        
        t.function(t.args); //执行任务
        
        //任务执行完毕,则busyNum-1
        pool->m.lock();
        pool->busyNum--;
        pool->m.unlock();
    }
    return nullptr;
}

void* myThreadPool::manage(void* args){
    //拿到对象的指针
    myThreadPool* pool = (myThreadPool*)args;
    
    //循环执行
    while(!pool->shutdown){
        sleep(5); //每5秒检测一次
        printf("manager开始工作\n");
        // 访问临界资源,并得到存活的线程数liveNum和任务数taskNum,作为判断线程数量是否合适的依据
        pool->m.lock();
        int liveNum = pool->liveNum;
        int taskNum = pool->taskQ.get_length(); //注意这里不能直接pool->get_task_num(),因为该函数内部也需要互斥锁pool->m,会出现死锁
        pool->m.unlock();
        
        //如果存活的线程太少,则增加线程,每次增加2个
        if(taskNum > liveNum && liveNum < pool->maxNum){
            unique_lock<mutex>ulock(pool->m); //上锁,if结束自动解锁
            int num=0;
            for(int i=0;i<pool->maxNum && pool->liveNum < pool->maxNum && num<2; ++i){
                if(pool->worker[i]==nullptr){
                    pool->worker[i] = new thread(work, pool); //新建线程
                    num++;
                    pool->liveNum++;
                }
            }
            cout<<"2 threads are added by manager\n";
        }
        
        //如果存活的线程太多,则减少线程数目,每次减少2个
        if(taskNum*2 < liveNum && liveNum > pool->minNum){
            unique_lock<mutex>ulock(pool->m); //上锁,if结束自动解锁
            // 这里不能指定杀死某个线程,因为并不知道线程的状态(空闲/忙碌),要让空闲的线程自己自杀
            if(pool->liveNum-2>pool->minNum)pool->deleteNum=2;
            cout<<"2 threads are deleted by manager\n";
        }
    }
    
    return nullptr;
}
void myThreadPool::add_task(task t){ //添加任务
    unique_lock<mutex> ulock(this->m); //加锁,函数结束自动释放锁
    this->taskQ.add(t); //向队列中添加任务
    
    this->condEmpty.notify_all();//告知工作线程有任务了
}
int myThreadPool::get_live_thread_num(){  //返回存活的线程数木
    unique_lock<mutex> ulock(this->m); //加锁,函数结束自动释放锁
    return this->liveNum;
}
int myThreadPool::get_busy_thread_num(){  //返回忙碌的线程数目
    unique_lock<mutex> ulock(this->m); //加锁,函数结束自动释放锁
    return this->busyNum;
}
int myThreadPool::get_task_num(){ // 返回任务队列中任务的数目
    unique_lock<mutex> ulock(this->m); //加锁,函数结束自动释放锁
    return this->taskQ.get_length();
}

void myThreadPool::thread_exit(){
    //找到当前的线程并删除
    thread::id this_id = this_thread::get_id(); //得到当前线程的id
    //找到该线程,并删除
    for(int i=0;i<this->maxNum;++i){
        if(this->worker[i]!=nullptr && this->worker[i]->get_id() == this_id){
//            delete this->worker[i]; //这里不能直接delete,因为会直接导致当前线程终止在这里,不执行下面的赋值nullptr的操作,从而出现野指针
            this->worker[i]=nullptr;  //worker[i]赋值为nullptr,表示没有线程
            this->liveNum--; //存活的线程数目-1
            break;
        }
    }
}

测试代码

#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include "myThreadPool.hpp"
using namespace std;

void test_task(void *args){
    int num = *(int*)args;
    printf("正在执行%d任务\n",num);
    sleep(1);
}
int main(){
    threadPool* pool = new threadPool(3, 10);
    for(int i=0;i<100;++i){
        
        int *num = new int(i);
        task t(test_task, num);
//        printf("123\n");
        pool->add_task(t);
    }
    
    while(pool->get_live_thread_num()!=0 && pool->get_task_num()){
        sleep(5);
        printf("存活线程数为%d,忙碌线程数为%d, 任务数为%d\n", pool->get_live_thread_num(), pool->get_busy_thread_num(),pool->get_task_num());
    }
    sleep(10); //等待10s,测试manager的减少线程的操作
    delete pool;
    
    return 0;
}

部分执行结果(macOS-Xcode):

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值