一、缓存线程池的概念
缓存线程池是一种动态大小的线程池实现,其有以下核心特点:
- 核心线程:启动时创建固定数量,这些线程永远不会被销毁,除非整个线程停止。
- 最大线程:线程总数有上限(本实现为 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 正常返回。