线程池:缓存线程池CachedThreadPool

一、缓存线程池的概念

缓存线程池是一种动态大小的线程池实现,其有以下核心特点:

  • 核心线程:启动时创建固定数量,这些线程永远不会被销毁,除非整个线程停止。
  • 最大线程:线程总数有上限(本实现为 2×硬件并发数 + 1),防止线程爆炸。
  • 按需创建:任务提交时,如果没有空闲线程且当前线程数未达上线,就立即创建线程
  • 空闲超时销毁:非核心线程空闲超过KeepAliveTime(本实现测试设为10秒)后,自动销毁自身,释放系统资源。
  • 任务队列:使用有界同步队列缓存待执行任务,当队列满时,提交者会直接在当前线程执行任务(Caller-Runs 拒绝策略),避免任务无限堆积。

与固定大小线程池不同,缓存线程池适合短期、突发、IO密集型任务场景(如Web服务器处理短连接请求)。它既避免了"频繁创建/销毁线程"的高昂开销,又不会像固定池那样长期占用过多线程。

二、缓存线程池设计理念

线程的创建和销毁成本很高(涉及内核态切换、栈内存分配、TLS 初始化等)。

  • 如果每次任务都 new std::thread,高并发下系统会崩溃
  • 如果固定很大数量的线程,又会浪费 CPU/内存(尤其是任务稀疏时)

缓存线程池的解决思路:

  • 复用:优先使用空闲线程
  • 动态伸缩:任务暴增时快速扩容,任务减少时自动缩容
  • 有界保护:队列容量小(本代码设为2),快速触发"创建新线程"或"调用者运行"机制
  • 优雅关闭:支持Stop()让所有线程安全退出,不丢失任务

这就是"缓存(Cached)"的含义------线程像缓存一样,按需加载、用完回收。

三、代码设计讲解

同步队列(SyncQueue)

SyncQueue.hpp:

cpp 复制代码
#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;
    }

 
    template<class F>
    int Add(F&& x)
    {//步骤1:调用 `wait_for` 后,先检查 lambda 谓词(不解锁锁);
        /*-若谓词返回 `true`(队列未满或已停止):直接返回 `true`,不阻塞,继续执行;
            - 若谓词返回 `false`(队列已满且未停止):进入阻塞流程。
            步骤2:阻塞流程(谓词为 `false` 时):
            a. `wait_for` 会自动 * *解锁 `locker`* * (关键!让其他线程能操作队列,比如出队线程取任务、停止线程设置 `m_needStop`);
            b.当前线程进入"阻塞状态"(不占用 CPU 资源),等待以下两种情况之一:
            ① 超时:等待时间达到 `m_waitTime` 秒;
            ② 被通知:其他线程调用 `m_notFull.notify_one()` 或 `notify_all()`(比如出队线程取走任务后,队列未满,会调用 `m_notFull.notify_one()` 通知入队线程)。
            步骤3:唤醒后处理:
            a.无论因"超时"还是"被通知"唤醒,`wait_for` 会先** 重新加锁 `locker`** (恢复锁的控制权,确保后续操作线程安全);
            b.再次检查 lambda 谓词:
            - 若谓词返回 `true`:返回 `true`(条件满足,继续执行);
            - 若谓词返回 `false`:返回 `false`(超时,队列仍满)。*/
        std::unique_lock<std::mutex> locker(m_mutex);
        if (!m_notFull.wait_for(locker, std::chrono::seconds(m_waitTime), //超时时间:最多等待 m_waitTime 秒。若超过这个时间仍未满足条件,函数会自动唤醒并返回 false。
            [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_needStop && 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();
    }
};

SyncQueue关键成员:

std::list m_queue:任务缓冲区(链表便于 move 整个队列)。

mutable std::mutex m_mutex + 两个条件变量:

m_notEmpty:消费者等待"有任务"。

m_notFull:生产者等待"有空位"。

m_maxSize(默认 100,测试中为 2):有界,防止内存爆炸。

m_waitTime(默认 1 秒):超时等待,避免无限阻塞。

m_needStop:停止标志,支持优雅关闭。

核心方法:

1.Add() (Put的实现)--- 生产者入队:

cpp 复制代码
if (!m_notFull.wait_for(locker, std::chrono::seconds(m_waitTime),
    [this] { return m_needStop || !IsFull(); }))

使用 predicate + wait_for 是 C++11 最佳实践:自动处理虚假唤醒,超时后直接返回失败(1)。

设计意图:队列满时最多等 1 秒,若仍满就返回失败(线程池里会让调用者直接执行任务)。

成功后 m_notEmpty.notify_one() 唤醒消费者。

2.Take(T& t) --- 消费者出队:

同理使用 wait_for + predicate,超时返回 1(空),停止返回 2。

出队后 m_notFull.notify_one() 唤醒生产者。

3.Take(std::list& list) --- 批量出队(Stop 时使用)。

4.Stop() --- 优雅停止:

先等待队列清空(while (!IsEmpty()) m_notFull.wait(locker);),再设置 m_needStop = true 并广播通知。

设计意图:确保所有任务被消费完再停止,避免任务丢失。

为什么要这么设计?

条件变量 + predicate 比 while + sleep 高效百倍(零 CPU 占用等待)。

超时机制 让线程池能"感知"空闲,进而实现空闲线程销毁。

停止标志 + 双通知 保证线程池关闭时所有工作线程能安全退出。

缓存线程池(CachedThreadPool)

CachedThreadPool.hpp:

cpp 复制代码
#include"SyncQueue.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();//清空哈希表 m_threadgroup,释放所有线程对象的资源。
    }

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()
    {
        Stop();
    }
    void Stop()
    {
        std::call_once(m_flag, [this] {StopThreadGroup();});
    }

    template<class Func, class... Args>
    void execute(Func&& func, Args&&... args)
    {
        auto task = std::make_shared<std::function<void()>>(std::bind(func, std::forward<Args>(args)...));

        if (m_queue.Put([task]() {(*task)();}) != 0)
        {
            (*task)();
        }
    }

    template<class Func, class... Args>
    auto submit(Func&& func, Args&&... args) -> std::future<decltype(func(args...))>//这是一个 可变参数模板函数,同时使用了「尾置返回类型」(C++11 特性),核心设计是「泛型兼容 + 结果可
    {
        using ReType = 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;
    }


};

CachedThreadPool.hpp中RunInThread() 的核心逻辑:

cpp 复制代码
while (m_running) {
    // 1. 空闲检测(仅在队列为空且等待超时后触发)
    if (m_queue.size() == 0 && m_queue.notTask()) {  // notTask() 内部 wait_for 1s
        // 检查是否空闲超过 KeepAliveTime 且可销毁
        if (intervalTime >= KeepAliveTime && m_curThreadSize > m_coreThreadSize) {
            // detach + erase,自行退出
            return;
        }
    }
    
    // 2. 取任务执行
    if (!m_queue.Take(task) && m_running) {  // Take 成功返回 0
        m_idleThreadSize--;
        task();
        m_idleThreadSize++;
        startTime = now;  // 重置空闲计时器
    }
}

execute() / submit():

先尝试 m_queue.Put(),失败(队列满)则调用者直接执行(Caller-Runs 策略)。

submit() 额外返回 std::future,支持获取结果。

创建新线程:if (m_idleThreadSize <= 0 && m_curThreadSize < m_maxThreadSize) → 立刻扩容。

整体设计意图:

队列容量故意设得很小(2),让"满"的状态快速出现,从而触发扩容或调用者运行。

空闲销毁放在 RunInThread 中,由线程自己判断(无需额外监控线程)。

使用 std::once_flag 保证 Stop() 只执行一次。

~CachedThreadPool() 自动调用 Stop(),RAII 风格。

测试代码

test.cpp:

cpp 复制代码
#include"CachedThreadPool.hpp"

//测试二
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;
}
#if 0
//测试一
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);
		}
	}
}


#endif

测试二

目的:

  • 验证动态扩容:4 个任务同时提交,核心只有 2 个,队列容量 2 → 必然触发新线程创建(最多可达 2×硬件并发+1)。
  • 验证任务并发执行:不同 sleep 时间,观察输出顺序是否体现并行(而非串行)。
  • 验证空闲超时销毁:20 秒后再次提交,观察是否打印"空闲线程销毁",证明非核心线程已被回收。
  • 验证 Caller-Runs:极端情况下队列满时,提交者是否自己执行任务(不会阻塞主线程)。
  • 验证 future 正确性:r.get() 是否能拿到正确结果。

测试一:1000 个任务混用 submit 和 execute,目的是压力测试线程复用、任务不丢失、future 正常返回。

相关推荐
小欣加油2 小时前
leetcode 128 最长连续序列
c++·算法·leetcode·职场和发展
玖釉-2 小时前
图形 API 的前沿试车场:Vulkan 扩展体系深度解析与引擎架构实践
c++·架构·图形渲染
许杰小刀2 小时前
SourceGenerator之partial范式及测试
c++·mfc
玖釉-2 小时前
告别 Shared Memory 瓶颈:Vulkan Subgroup 架构解析与硬核实战指南
开发语言·c++·windows·图形渲染
吴梓穆2 小时前
UE5 C++ 两种枚举
开发语言·c++·ue5
星辰徐哥2 小时前
C++测试与调试:确保代码质量与稳定性
开发语言·c++
jghhh012 小时前
VC++ 屏幕锁定、关机、托盘工具源代码
开发语言·c++
邪修king2 小时前
【UE4/UE5 萌新向】有C++基础如何快速入门虚幻引擎?超详细图文全揭秘!
c++·ue5·ue4
Imxyk2 小时前
P9244 [蓝桥杯 2023 省 B] 子串简写
数据结构·c++·算法