C++ | 简单线程池的实现

目录

实现原理

1. 该线程池的实现实体有两个分别为事务队列和线程队列。

2. 对任务队列的操作采取生产者-消费者模型。

代码实现细节

1. 代码实现环境:VSCode平台与Ubuntu环境

2. using Task = function;(void)>

3. void start(int numThreads);//创建线程

4. void ThreadPool::stop()//停止线程执行

(1)用C11特性 | 范围for实现

(2)用库函数 | for_each()实现

(3)用vector的迭代器 | 迭代器iterator实现

5. 函数void ThreadPool::runInthread()

6. 函数void ThreadPool::run(const Task &cb)

代码实现(有详细注释)

1. ThreadPool.hpp

2. ThreadPool.cpp


实现原理

1. 线程池的实现实体有两个分别为事务队列和线程队列

事务队列用双端队列deque封装,存放的为事务(其实为void(void)类型函数);线程池用vector容器封装,vector中存放唯一性智能指针,智能指针指向线程对象(创建线程将其和函数runInthread绑定)。每个线程创建后会先调用设置的回调函数threadInitCallback(事务),接着去事务队列取事务并执行。

2. 对任务队列的操作采取生产者-消费者模型

 定义一个全局锁变量mutex(保证取事务和添加事务时的互斥性!+两个条件变量empty(负责管理当取事务而事务队列为空时empty加入条件变量等待队列阻塞empty.wait(mtx);当要结束线程时将标志状态running设置为false且唤醒所有阻塞在等待队列的条件变量empty令take()返回空事务结束执行)和full(当要添加事务进事务队列发现事务队列是满的时full.wait(mtx)阻塞;当取出事务后发现事务队列有空间时唤醒阻塞的条件变量full.notify_all();)

代码实现细节

1. 代码实现环境:VSCode平台与Ubuntu环境

VSCode终端窗口编译代码: g++ -std=c++0x -pthread ThreadPool.cpp -o threadpool

2. using Task = function<void(void)>;

using的用法同typedef,此处是定义事务类型Task为void(void)类型函数的别名function<>为可调用对象包装器,function<void(void)>也可以认为是一个函数类型或函数对象(包装了void()类型的函数)。

代码中的回调函数即为可被调动执行的事务,为事务类型:Task threadInitCallback;

3. void start(int numThreads);//创建线程

该函数主要是启动线程(修改标志:running=true;)且将线程对象和成员函数runInthread绑定并添加进线程集合threads.push_back(std::unique_ptr<std::thread>(new thread(&ThreadPool::runInthread, this)));

4. void ThreadPool::stop()//停止线程执行

此函数可以直接调用唯一性智能指针的clear()函数实现对线程对象的清除,但在这之前必须先join()!!!如下:

(1)用C11特性 | 范围for实现

for(auto&x:threads)
{
    x->join();
}

(2)用库函数 | for_each()实现

std::for_each(threads.begin(), threads.end(), std::bind(&std::thread::join(), std::placeholders::_1));

此处传递join()为线程成员函数,故需要绑定一个占位参数即为成员函数隐藏的参数this指针

(3)用vector的迭代器 | 迭代器iterator实现

for (std::vector<std::unique_ptr<std::thread>>::iterator it = threads.begin(); it != threads.end(); ++it)
{
    (*it)->join();//(*it)就是取vector中的元素~指向线程对象的智能指针
}

5. 函数void ThreadPool::runInthread()

每个创建的线程均要和该函数绑定执行。该函数一开始执行回调函数(当回调函数已被注册即不为空时,每个新创建的线程都要先调用执行回调函数/可执行对象),接下来从事务队列取事务并执行。

6. 函数void ThreadPool::run(const Task &cb)

如果线程队列为空~代表就只有该进程自身->直接调用运行要添加进事务队列的事务即可。

否则加锁互斥访问事务队列并将传入函数的事务加入事务队列!加入后唤醒因取事务发现事务队列为空而被阻塞的empty条件变量

代码实现(有详细注释)

1. ThreadPool.hpp

#ifndef THREADPOOL_H
#define THREADPOOL_H

#include<functional> //bind()
#include<mutex>
#include<condition_variable>
#include<thread>
#include<queue>
#include<memory>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

namespace Yang
{
    class ThreadPool
    {
    public:
        using Task = function<void(void)>;
    private:
        std::vector<std::unique_ptr<std::thread>> threads;
        std::deque<Task> queue;//事务队列,存放事务(Task类型,void(void))
        std::mutex mtx;
        std::condition_variable empty;
        std::condition_variable full;
        std::string name;//线程名(构造函数赋初值!)
        Task threadInitCallback;//回调函数 Task仿函数类型 void(void)
        size_t maxQueueSize;//事务的个数
        bool running;//标志线程是否运行
        bool isFull() const;
        void runInthread();
        Task take();//获取任务

    public:
        explicit ThreadPool(const string &name = string("ThreadPool"));
        ~ThreadPool();
        void setMaxQueueSize(int maxsize);
        void setThreadCallback(const Task &cb);//设置回调函数
        void start(int numThreads);//创建线程
        void stop();//停止线程
        void run(const Task &cb);
    };
} // namespace Yang

#endif

2. ThreadPool.cpp

#include"ThreadPool.hpp"
#include<unistd.h>

namespace Yang
{
    using Task = function<void(void)>;

    bool ThreadPool::isFull() const
    {
        return maxQueueSize > 0 && maxQueueSize == queue.size();
    }

    void ThreadPool::runInthread()//执行线程~执行回调函数(执行任务)
    {
        if(threadInitCallback!=nullptr)
        {
            threadInitCallback();//每个线程启动后都要执行回调函数!!
        }
        //接下来从事务队列取事务并执行!
        while(running)
        {
            Task task(take());//定义事务并初始化(取事务)!
            if(task!=nullptr)
            {
                task();
            }
        }
    }

    Task ThreadPool::take()//获取任务,先加锁!!(线程执行状态取事务~为空则阻塞;添加事务~唤醒阻塞队列)
    {
        std::unique_lock<std::mutex> lock(mtx);
        while(running&&queue.empty())
        {
            empty.wait(lock);//阻塞到空的条件变量等待队列
        }
        Task task; // Task task=nullptr;
        if(!running)
            return task;//线程处于非运行态~直接返回空事务!!!!
        
        //线程在运行且事务队列不为空
        if(!queue.empty())
        {
            task = queue.front();
            queue.pop_front();
            // if(maxQueueSize>0) //事务队列还有事务~唤醒阻塞的条件变量
            // {
            //     full.notify_all();
            // }
            if(queue.size()<maxQueueSize) //如果还有位置~唤醒放入事务时因满阻塞的条件变量!
            {
                full.notify_all();
            }
        }
        return task;
    }

    ThreadPool::ThreadPool(const string &name):mtx{},full{},empty{},name{name},maxQueueSize{0},running{false}{ }

    ThreadPool::~ThreadPool()//将start()中new的线程对象均释放!
    {
        if(running)
        {
            stop();
        }
    }

    void ThreadPool::setMaxQueueSize(int maxsize) //事务队列大小
    {
        maxQueueSize = maxsize;
    }

    void ThreadPool::setThreadCallback(const Task &cb)//设置回调函数
    {
        threadInitCallback = cb;
    }

    void ThreadPool::start(int numThreads) //启动并设置线程个数
    {
        running = true;
        for (int i = 0; i < numThreads;++i)
        {
            //启动线程~将线程和runInthread函数绑定(引用,类成员函数传this指针)绑定,执行runInthread函数
            threads.push_back(std::unique_ptr<std::thread>(new thread(&ThreadPool::runInthread, this)));
        }
        if(numThreads==0)
            threadInitCallback();//如果线程=0~只有当前进程,直接执行回调函数(可执行对象)
    }

    void ThreadPool::stop()//停止线程执行
    {
        {   
            //避免多线程,start和stop同时执行~应该将对running的改变使用互斥锁封装到块内
            std::unique_lock<std::mutex> lock(mtx);
            running = false;
            //把34行阻塞到空队列的全唤醒!标志线程不运行,让他们执行下一句返回空事务!!
            empty.notify_all();
        }

        //每个线程对象先调用join()
        /*for(auto&x:threads)
        {
            x->join();
        }*/

        //绑定线程自己的join()函数,成员函数第一个参数为this指针,绑定器绑定!
        //std::for_each(threads.begin(), threads.end(), std::bind(&std::thread::join(), std::placeholders::_1));

        for (std::vector<std::unique_ptr<std::thread>>::iterator it = threads.begin(); it != threads.end(); ++it)
        {
            (*it)->join();//(*it)就是取vector中的元素~指向线程对象的智能指针
        }

        threads.clear(); //调用唯一性智能指针的清除函数~可以实现delete指向的线程对象!(但在这之前必须先join!!!)
    }

    void ThreadPool::run(const Task &cb) //添加事务并调动事务运行(没线程~直接运行事务;否则若事务队列不满~加锁添加事务)
    {
        if(threads.empty())
        {
            cb();
        }
        std::unique_lock<std::mutex> lock(mtx);
        while(isFull())
        {
            full.wait(lock);
        }
        queue.push_back(cb);
        empty.notify_all();
    }
}


#include<iostream>
using namespace std;
void print()
{
    cout << "InitThread" << endl;
}
void printString(const string& str)
{
    cout << "string:" << str << endl;
}
class Test
{
private:
    std::string str;
public:
    Test(const string& s="Yangyang"):str(s){}
    void operator()()
    {
        cout<<"Test::string="<< str<<endl;
    }
};
void funa(Yang::ThreadPool& the)
{
    Test te{};
    for (int i = 0; i < 5;++i)
    {
        the.run(te);
    }
}

int main()
{
    Test te{};//该类重载小括号~为可执行对象
    Yang::ThreadPool threadPool("Yang::ThreadPool");
    threadPool.setMaxQueueSize(2);//2个事务
    threadPool.setThreadCallback(print);//注册回调。每个线程启动后都要调用print
    threadPool.start(10);//5个线程

    //threadPool.run(std::bind(printString, "Yang"));//不能直接传printString给run()运行!!事务类型:void(void),绑定器可以解决!!
    //threadPool.run(te);
    //由于程序运行太快~导致后面的来不及执行->sleep()
    //sleep(5);//unistd.h

    std::thread tha(funa,std::ref(threadPool));
    tha.join();

    threadPool.stop();
    return 0;
}

 如有不足希望大家指出,谢谢支持!╰(*°▽°*)╯

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值