C++实现线程池(3)缓存线程池

三. CachedThreadPool 的实现

3.1 需求:

动态调整线程数量:与 FixedThreadPool 不同,CachedThreadPool 的线程数量是动态调整的。当有新任务提交时,如果线程池中有空闲的线程,则会立即使用空闲线程执行任务;如果线程池中没有空闲线程,则会创建一个新的线程来执行任务。当线程空闲一段时间后,超过一定的时间(默认为 60 秒),会被回收销毁。

3.2 SyncQueue 同步队列的设计和实现

复制代码
#ifndef SYNCQUEUE3_HPP
#define SYNCQUEUE3_HPP

#include<list>
#include<mutex>
#include<condition_variable>
#include<iostream>

using namespace std;

template<class T>
class SyncQueue
{
private:
    std::list<T> m_queue; // 任务缓冲区
    mutable std::mutex m_mutex;
    std::condition_variable m_notEmpty; // C
    std::condition_variable m_notFull;  // P
    size_t m_waitTime;                  //任务队列满等待时间s
    int m_maxSize;                      // 
    bool m_needStop;    // true 同步队列不在接受新的任务//

    bool IsFull() const
    {
        bool full = m_queue.size() >= m_maxSize;
        if (full)
        {
            printf("m_queue 已经满了,需要等待....\n");
        }
        return full;
    }

    bool IsEmpty() const
    {
        bool empty = m_queue.empty(); //
        if (empty)
        {
            printf("m_queue 已经空了,需要等待....\n");
        }
        return empty;
    }

    // return 0; 成功
    // 1 ;// full
    // 2 ;// stop;
    template<class F>
    int Add(F&& x)
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        if (!m_notFull.wait_for(locker, std::chrono::seconds(m_waitTime),
            [this] { return m_needStop || !IsFull(); }))
        {
            cout << "task queue full return 1" << endl;
            return 1;
        }
        if (m_needStop)
        {
            cout << "同步队列停止工作..." << endl;
            return 2;
        }
        m_queue.push_back(std::forward<F>(x));
        m_notEmpty.notify_one();
        return 0;
    }

public:
    SyncQueue(int maxsize = 100, int timeout = 1)
        :m_maxSize(maxsize),
        m_waitTime(timeout),
        m_needStop(false) // 同步队列开始工作
    {}

    int Put(const T& x)
    {
        return Add(x);
    }

    int Put(T&& x)
    {
        return Add(std::forward<T>(x));
    }

    int notTask()
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        if (!m_notEmpty.wait_for(locker, std::chrono::seconds(m_waitTime))
            == std::cv_status::timeout)
        {
            return 1;
        }
        return 0;
    }

    void Take(std::list<T>& list) //
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        while (!m_needStop && IsEmpty())
        {
            m_notEmpty.wait(locker);
        }
        if (m_needStop)
        {
            cout << "同步队列停止工作..." << endl;
            return;
        }
        list = std::move(m_queue);
        m_notFull.notify_one();
    }

    //T& GetTake();
    // return 0; 成功
    // 1 ;// empty
    // 2 ;// stop;
    int Take(T& t) // 1
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        if (!m_notEmpty.wait_for(locker, std::chrono::seconds(m_waitTime),
            [this] { return m_needStop || !IsEmpty(); }))
        {
            return 1;   // 队列空
        }
        if (m_needStop)
        {
            cout << "同步队列停止工作..." << endl;
            return 2;
        }
        t = m_queue.front();
        m_queue.pop_front();
        m_notFull.notify_one();
        return 0;
    }

    void Stop()
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        while (!IsEmpty())
        {
            m_notFull.wait(locker);
        }
        m_needStop = true;
        m_notFull.notify_all();
        m_notEmpty.notify_all();
    }

    bool Empty() const
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        return m_queue.empty();
    }

    bool Full() const
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        return m_queue.size() >= m_maxSize;
    }

    size_t size() const
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        return m_queue.size();
    }
};

#endif

3.3 CachedThreadPool 线程池的设计和实现

复制代码
#ifndef CACHEDTHREADPOOL_HPP
#define CACHEDTHREADPOOL_HPP

#include"SyncQueue3.hpp"
#include<functional>
#include<unordered_map>
#include<map>
#include<future>

using namespace std;

int MaxTaskCount = 2;
const int KeepAliveTime = 10;   //线程最大存活时间 60 ,为测试改为10

class CachedThreadPool
{
public:
    using Task = std::function<void(void)>;

private:
    std::unordered_map<std::thread::id, std::shared_ptr<std::thread>> m_threadgroup;
    int m_coreThreadSize;                 // 核心的线程数量,下限阈值 2
    int m_maxThreadSize;                  // 最大的线程数量,上限阈值
    std::atomic_int m_idleThreadSize;     // 空闲线程的数量
    std::atomic_int m_curThreadSize;      // 当前线程池里面的线程总数量
    mutable std::mutex m_mutex;           // 
    SyncQueue<Task> m_queue;
    std::atomic_bool m_running; // true ; false stop;
    std::once_flag m_flag;

    void Start(int numthreads)
    {
        m_running = true;
        m_curThreadSize = numthreads;
        for (int i = 0; i < numthreads; ++i)
        {
            auto tha = std::make_shared<std::thread>(std::thread(&CachedThreadPool::RunInThread, this));
            std::thread::id tid = tha->get_id();
            m_threadgroup.emplace(tid, std::move(tha));
            m_idleThreadSize++;
        }
    }

    void RunInThread()
    {
        auto tid = std::this_thread::get_id();
        auto startTime = std::chrono::high_resolution_clock().now();
        while (m_running)
        {
            Task task;
            if (m_queue.size() == 0 && m_queue.notTask())
            {
                auto now = std::chrono::high_resolution_clock().now();
                auto intervalTime = std::chrono::duration_cast<std::chrono::seconds>(now - startTime);
                std::lock_guard<std::mutex> lock(m_mutex);
                if (intervalTime.count() >= KeepAliveTime &&
                    m_curThreadSize > m_coreThreadSize)
                {
                    m_threadgroup.find(tid)->second->detach();
                    m_threadgroup.erase(tid);
                    m_curThreadSize--;
                    m_idleThreadSize--;
                    cout << "空闲线程销毁" << m_curThreadSize << " " << m_coreThreadSize << endl;
                    return;
                }
            }
            if (!m_queue.Take(task) && m_running)
            {
                m_idleThreadSize--;
                task();
                m_idleThreadSize++;
                startTime = std::chrono::high_resolution_clock().now();
            }
        }
    }

    void StopThreadGroup()
    {
        m_queue.Stop();
        m_running = false;
        for (auto& thread : m_threadgroup)
        {
            thread.second->join();
        }
        m_threadgroup.clear();
    }

public:
    CachedThreadPool(int initNumThreads = 8, int taskPoolSize = MaxTaskCount)
        :m_coreThreadSize(initNumThreads),
        m_maxThreadSize(2 * std::thread::hardware_concurrency() + 1),
        m_idleThreadSize(0),
        m_curThreadSize(0),
        m_queue(taskPoolSize),
        m_running(false)
    {
        Start(m_coreThreadSize);
    }

    ~CachedThreadPool()
    {
        StopThreadGroup();
    }

    template<class Func, class... Args>
    auto submit(Func&& func, Args&&... args) -> std::future<decltype(func(args...))>
    {
        auto task = std::make_shared<std::packaged_task<decltype(func(args...))()>>(
            std::bind(std::forward<Func>(func), std::forward<Args>(args)...)
            );
        std::future<decltype(func(args...))> result = task->get_future();
        if (m_queue.Put([task]() { (*task)(); }) != 0)
        {
            cout << "调用者运行策略" << endl;
            (*task)();
        }
        if (m_idleThreadSize <= 0 && m_curThreadSize < m_maxThreadSize)
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            auto tha = std::make_shared<std::thread>(std::thread(&CachedThreadPool::RunInThread, this));
            std::thread::id tid = tha->get_id();
            m_threadgroup.emplace(tid, std::move(tha));
            m_curThreadSize++;
            m_idleThreadSize++;
        }
        return result;
    }

    template<class Func, class... Args>
    void execute(Func&& func, Args&&... args)
    {
        submit(std::forward<Func>(func), std::forward<Args>(args)...);
    }
};

#endif

3.4 测试

复制代码
///
void func(int index)
{
    static int num = 0;
    cout << "func_" << index << " num: " << ++num << endl;
}

int add(int a, int b)
{
    return a + b;
}

int main()
{
    CachedThreadPool mypool;
    for (int i = 0; i < 1000; ++i)
    {
        if (i % 2 == 0)
        {
            auto pa = mypool.submit(add, i, i + 1);
            cout << pa.get() << endl;
        }
        else
        {
            mypool.execute(func, i);
        }
    }

    CachedThreadPool pool(2);

    int add(int a, int b, int s)
    {
        std::this_thread::sleep_for(std::chrono::seconds(s));
        int c = a + b;
        cout << "add begin ..." << endl;
        return c;
    }

    void add_a()
    {
        auto r = pool.submit(add, 10, 20, 4);
        cout << "add_a: " << r.get() << endl;
    }

    void add_b()
    {
        auto r = pool.submit(add, 20, 30, 6);
        cout << "add_b: " << r.get() << endl;
    }

    void add_c()
    {
        auto r = pool.submit(add, 30, 40, 1);
        cout << "add_c: " << r.get() << endl;
    }

    void add_d()
    {
        auto r = pool.submit(add, 10, 40, 9);
        cout << "add_d: " << r.get() << endl;
    }

    int main()
    {
        std::thread tha(add_a);
        std::thread thb(add_b);
        std::thread thc(add_c);
        std::thread thd(add_d);

        tha.join();
        thb.join();
        thc.join();
        thd.join();

        std::this_thread::sleep_for(std::chrono::seconds(20));

        std::thread the(add_a);
        std::thread thf(add_b);

        the.join();
        thf.join();

        return 0;
    }
}

3.5 FixedThreadPool 与 CachedThreadPool 特性对比

特性 FixedThreadPool CachedThreadPool
重用 能 reuse 就用,但不能随时建新的线程 先查看池中有无以前建立的线程,有就 reuse;没有就建新线程加入池中
池大小 可指定 nThreads,固定数量 可增长,最大值 Integer.MAX_VALUE
队列大小 无限制 无限制
超时 无 IDLE 默认 60 秒 IDLE
使用场景 针对稳定固定的正规并发线程,用于服务器,执行负载重、CPU 使用率高的任务,防止线程频繁切换得不偿失 处理大量短生命周期异步任务,执行大量并发短期异步任务,任务负载要轻
结束 不会自动销毁 放入的线程超过 TIMEOUT 不活动会自动被终止

3.6 最佳实践

FixedThreadPool 和 CachedThreadPool 对高负载应用都不特别友好,CachedThreadPool 更危险。若应用要求高负载、低延迟,最好不选这两种,推荐使用 ThreadPoolExecutor ,可进行细粒度控制:

  1. 将任务队列设置成有边界的队列
  2. 使用合适的 RejectionHandler 拒绝处理程序
  3. 若任务完成前后需执行操作,可重载 beforeExecute(Thread, Runnable)afterExecute(Runnable, Throwable)
  4. 重载 ThreadFactory ,若有线程定制化需求
  5. 运行时动态控制线程池大小(Dynamic Thread Pool)

3.7 使用场景

适用于以下场景:

  1. 大量短期任务:适合处理大量短期任务,任务到来时尽可能创建新线程执行,有空闲线程则复用,避免频繁创建销毁线程的额外开销。
  2. 任务响应快速:可根据任务到来快速创建启动新线程执行,减少任务等待时间。
  3. 不需要限制线程数量:最大线程数不限,只要内存足够,可根据任务动态创建新线程。
  4. 短期性任务的高并发性:可动态创建线程,适合处理需高并发性的短期任务,任务处理完后保持一定空闲线程用于下一批任务。

需注意,CachedThreadPool 线程数量不受限,任务过多可能导致线程数量过多、系统资源过度消耗,使用时需灵活调整线程数量或用其他线程池控制资源。

相关推荐
Dream it possible!24 分钟前
LeetCode 面试经典 150_数组/字符串_O(1)时间插入、删除和获取随机元素(12_380_C++_中等)(哈希表)
c++·leetcode·面试·哈希表
意疏27 分钟前
浙江大学PTA程序设计C语言基础编程练习题6-10
c语言·开发语言
AI必将改变世界32 分钟前
【软考系统架构设计师备考笔记5】 - 专业英语
java·开发语言·人工智能·笔记·系统架构·英语
listhi52036 分钟前
Python实现信号小波分解与重构
开发语言·python·重构
骑驴看星星a1 小时前
层次分析法代码笔记
开发语言·笔记·python·numpy
PaperFly2 小时前
📘 C++ 中成员函数与普通函数的地址是否唯一?
c++
悟纤3 小时前
Suno API V5模型 python源码 —— 使用灵感模式进行出创作
开发语言·python·suno·ai音乐
寻星探路3 小时前
常用排序方法
java·开发语言·算法
半桔3 小时前
【STL源码剖析】从源码看 vector:底层扩容逻辑与内存复用机制
java·开发语言·c++·容器·stl