线程池及C++代码实现

概念

    线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。在JAVA中主要是使用ThreadPoolExecutor类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池(不推荐使用)。

好处

  • 降低资源消耗:降低线程创建和销毁造成的损耗.

  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。

  • 提高线程的可管理性:线程的不合理分布导致资源调度失衡,降低系统的稳定性

  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

    线程池通常适合下面的几个场合:

1单位时间内处理任务频繁而且任务处理时间短;

2对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。

任务执行机制

图片

线程池业务场景

  • 快速响应用户需求:调高corePoolSize 和 maxPoolSize

  • 快速处理批量任务:调整合适的corePoolSize

C++实现线程池

    因为C++不像Java那样有现成的线程池库,所以需要自己写代码进行实现。

    参考的源码地址:https://github.com/progschj/threadpool

实现思想

    线程池的实现思想:“管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。”

代码

    主要只有一个文件:ThreadPool.h

#ifndef THREAD_POOL_H#define THREAD_POOL_H#include <vector>#include <queue>#include <memory>#include <thread>#include <mutex>#include <condition_variable>#include <future>#include <functional>#include <stdexcept>class ThreadPool {public:    // 构造函数    ThreadPool(size_t);    // 添加任务函数    template<class F, class... Args>    auto enqueue(F&& f, Args&&... args)         -> std::future<typename std::result_of<F(Args...)>::type>;    // 析构函数    ~ThreadPool();private:    // need to keep track of threads so we can join them    std::vector< std::thread > workers;         // 线程数组    // the task queue    std::queue< std::function<void()> > tasks;  // 任务队列    // synchronization    std::mutex queue_mutex;             // 互斥量    std::condition_variable condition;  // 条件变量    bool stop;                          // 停止标志位};// the constructor just launches some amount of workers(stop = false)inline ThreadPool::ThreadPool(size_t threads)    :   stop(false){    for(size_t i = 0;i<threads;++i)        // 依次调用线程处理        workers.emplace_back(            [this]            {                for(;;)                {                    std::function<void()> task;                    {                        // 对互斥量加锁                        std::unique_lock<std::mutex> lock(this->queue_mutex);                        // 等待lambda函数返回值产生                        // 如果没有任务 / stop了,则阻塞,互斥量解锁 => 释放给其他线程                        // 有任务 / 没有stop,则不阻塞线程,互斥量加锁 => 处理任务                        this->condition.wait(lock,                            [this]{ return this->stop || !this->tasks.empty(); });                        // 没有处理任务=>直接返回                        if(this->stop && this->tasks.empty())                            return;                        // 从任务队列中取出任务                        task = std::move(this->tasks.front());                        this->tasks.pop();                    }                    task(); // 处理任务                    // unique_lock自动解锁                }            }        );}// add new work item to the pooltemplate<class F, class... Args>auto ThreadPool::enqueue(F&& f, Args&&... args)     -> std::future<typename std::result_of<F(Args...)>::type>{    // 指定“返回值类型”别名    using return_type = typename std::result_of<F(Args...)>::type;    // 将bind()函数包装起来并分配其智能指针给task变量    auto task = std::make_shared< std::packaged_task<return_type()> >(            std::bind(std::forward<F>(f), std::forward<Args>(args)...)        );    // 获取task的future对象    std::future<return_type> res = task->get_future();    {        // 加锁        std::unique_lock<std::mutex> lock(queue_mutex);        // don't allow enqueueing after stopping the pool        // 如果停止使用线程池,则不可以添加任务;        if(stop)            throw std::runtime_error("enqueue on stopped ThreadPool");        // 添加任务        tasks.emplace([task](){ (*task)(); });        // 互斥量自动解锁    }    // 把wait()线程唤醒=>解除阻塞到condition条件变量的线程    condition.notify_one();    return res;}// the destructor joins all threadsinline ThreadPool::~ThreadPool(){    {        std::unique_lock<std::mutex> lock(queue_mutex);        // 停止使用线程池        stop = true;    }    // 唤醒所有wait()    condition.notify_all();    // 所有线程汇合    for(std::thread &worker: workers)        worker.join();}#endif

代码详细解释

    首先我们看构造函数:inline ThreadPool::ThreadPool(size_t threads) : stop(false){},这个函数向线程队列worker插入threads个线程,每个线程对象用一个匿名函数lambda来初始化,每个匿名函数会一直循环着从任务队列中取出任务来执行。它首先对互斥量加锁,获得互斥锁后,调用条件变量的wait()函数等待条件发生,传入wait()函数的第二个参数为一个匿名函数lambda,当函数返回值为false时,wait会使得该线程阻塞(任务队列为空时会阻塞),并且对互斥量进行解锁。当函数返回值为true(任务队列不为空)并且收到条件变量的通知后,wait函数使线程解阻塞,并且对互斥量加锁。接着从任务队列里面弹出一个任务,对互斥量自动解锁,并执行这个任务。

    接着我们看添加任务的函数:auto ThreadPool::enqueue(F&& f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type>{},这个函数向任务队列中加入一个任务。它首先创建一个名为task的智能指针,接着对互斥量加锁,然后把该任务加入到任务队列中,对互斥量自动解锁。调用条件变量的notify_one()函数,使得阻塞到这个条件变量的线程解阻塞。

    总结:初始化时,线程中的每个函数都会阻塞到条件变量那里,当任务队列中新加入一个任务时,通知阻塞到条件变量的某一个线程,接着这个线程执行:互斥量加锁——>任务队列出队——>互斥量解锁——>执行任务。当线程执行完任务之后,如果任务队列不为空,则继续从任务队列那里取出任务执行,如果任务队列为空则阻塞到条件变量那里。

使用方法

    代码配套的example.cpp

#include <iostream>#include <vector>#include <chrono>#include "ThreadPool.h"int main(){    // 线程池大小为4    ThreadPool pool(4);    std::vector< std::future<int> > results;    // 添加8个任务给线程池处理    for(int i = 0; i < 8; ++i) {        // 获取任务结果        results.emplace_back(            pool.enqueue([i] {                std::cout << "hello " << i << std::endl;                std::this_thread::sleep_for(std::chrono::seconds(1));                std::cout << "world " << i << std::endl;                return i*i;            })        );    }    // 打印任务处理结果    for(auto && result: results)        std::cout << result.get() << ' ';    std::cout << std::endl;    return 0;}

 

    运行结果:

图片

posted @ 2023-06-26 23:59  冰山奇迹  阅读(285)  评论(0)    收藏  举报