需求:
动态调整线程数量:与 FixedThreadPool 不同,CachedThreadPool 的线程数量是动态调整的。当有新任务提交时,如果线程池中有空闲的线程,则会立即使用空闲线程执行任务;如果线程池中没有空闲线程,则会创建一个新的线程来执行任务。当线程空闲一段时间后,超过一定的时间(默认为 60 秒),会被回收销毁。
按需创建线程,空闲超时销毁
任务来了 → 有空闲线程? → 是 → 使用空闲线程
↓ 否
创建新线程 → 执行任务
线程空闲 → 超过60秒? → 是 → 销毁线程
↓ 否
保留线程
实现简易缓存线程池
cpp
#ifndef CACHED_THREADPOOL.HPP
#define CACHED_THREADPOOL.HPP
#include<iostream>
#include<atomic>
#include<mutex>
#include<queue>
#include<vector>
#include<functional>
#include<condition_variable>
#include<unordered_map>
#include<thread>
using namespace std;
class cachedthreadpool{
private:
unordered_map<thread::id,shared_ptr<thread>>threads;//存储活跃线程 可以快速根据id查找,删除指定线程(超时销毁)
queue<function<void()>>tasks;
mutex mtx;
condition_variable condition;
atomic<bool>stop{false};
int maxthreads;
atomic<int> idlethreads{0};//空闲线程计数器 判断是否需要创新线程
void createThread() {
auto thread = make_shared<std::thread>([this]() {
auto lastWorkTime = chrono::steady_clock::now();
while (!stop) {
function<void()> task;
// 等待任务
unique_lock<mutex> lock(mtx);
idlethreads++;
// 等待任务或超时
// 条件变量的等待逻辑:wait_for会阻塞当前线程,直到满足以下三个条件之一:
// 1. 超时:等待了2秒(参数指定的时间)
// 2. 线程池停止:stop == true
// 3. 有任务可执行:tasks队列不为空
// 注意:wait_for会自动释放锁,在被唤醒时重新获取锁,避免死锁
if (condition.wait_for(lock, chrono::seconds(2), [this] { return stop || !tasks.empty(); })) {
if (!tasks.empty()) {// 有任务
task = tasks.front();
tasks.pop();
}
} else {
// 超时,检查是否应该销毁
auto now = chrono::steady_clock::now();//获取「当前这一刻」的时间点对象;
auto idleTime = chrono::duration_cast<std::chrono::seconds>(now - lastWorkTime);//时间库的核心转换工具 程序的空闲时长;
if (idleTime.count() >= 2 && threads.size() > 1) {//当本线程空闲≥2 秒,且线程池里的线程不止一个时,就主动退出,销毁自己,给线程池减负
auto id = this_thread::get_id();// 销毁自己
threads.erase(id);
idlethreads--;
return;
}
continue;
}
idlethreads--;
if (task) { // 执行任务
task();
lastWorkTime = chrono::steady_clock::now();
}
}
});
threads[thread->get_id()] = thread;
}
public:
cachedthreadpool(int max=20):maxthreads(max){}
~cachedthreadpool(){
stop=true;
condition.notify_all();
for(auto& [id,thread]:threads){
if(thread->joinable())thread->join();
}
}
void submit(function<void()>task){
unique_lock<mutex>lock(mtx);
if(idlethreads==0&&threads.size()<maxthreads){
createThread();
}
tasks.push(task);
condition.notify_one();
}
};
#endif
实现完整缓存线程池:
实现syncqueue.hpp
cpp
#ifndef SYNCQUEUE.HPP
#define SYNCQUEUE.HPP
#include<iostream>
#include<mutex>
#include<atomic>
#include<thread>
#include<vector>
#include<queue>
#include<chrono>
#include<condition_variable>
#include<functional>
using namespace std;
template<typename T>
class syncqueue{
private:
queue<T>tasks;
condition_variable noempty;
mutable mutex m_mtx;
bool isstop;
size_t maxsize;
public:
syncqueue(int maxsize=100):maxsize(100),isstop(false){}
bool put(const T&task){//放任务
lock_guard<mutex>lock(m_mtx);
if(isstop)return false;
tasks.push(task);
noempty.notify_one();
return true;
}
bool take(T&task){
unique_lock<mutex>lock(m_mtx);
noempty.wait(lock,[this](){return isstop||!tasks.empty();});
if(isstop&&tasks.empty())return false;
task=tasks.front();
tasks.pop();
return true;
}
bool trytake(T&task){
lock_guard<mutex>lock(m_mtx);
if(tasks.empty())return false;
task=tasks.front();
tasks.pop();
return true;
}
bool notaskforseconds(int seconds){//检查队列是否长时间无任务
unique_lock<mutex>lock(m_mtx);
return noempty.wait_for(lock,chrono::seconds(seconds))==cv_status::timeout;
}
~syncqueue(){
stop();
}
void stop(){
lock_guard<mutex>lock(m_mtx);
isstop=true;
noempty.notify_all();
}
size_t size()const{
lock_guard<mutex>lock(m_mtx);
return tasks.size();
}
bool empty()const{
lock_guard<mutex>lock(m_mtx);
return tasks.empty();
}
bool Isstop()const{
lock_guard<mutex>lock(m_mtx);
return isstop;
}
};
#endif
实现cachedthreadpool
代码:
cpp
#ifndef CACHED_THREADPOOL.HPP
#define CACHED_THREADPOOL.HPP
#include<iostream>
#include<atomic>
#include<mutex>
#include<queue>
#include<vector>
#include<functional>
#include<condition_variable>
#include<unordered_map>
#include<thread>
#include<future>
#include"syncqueueforcached.hpp"
using namespace std;
class cachedthreadpool{
public:
using task=function<void()>;
private:
vector<thread>workers;
atomic<int>idlethreadcount;//空闲线程数
atomic<int>totalthreadcount;
const int corethreadsize;//核心线程数(最少保留)
const int maxthreadsize;
const int keepalivetime;//空闲存活时间
syncqueue<task>taskqueue;
atomic<bool>isrunning;
once_flag isstop;
void create_newthread(){
if(totalthreadcount>=maxthreadsize)return ;
workers.emplace_back(&cachedthreadpool::worker,this);
totalthreadcount++;
idlethreadcount++;
}
void stop_allthreads(){
isrunning=false;
taskqueue.stop();
for(auto& thread:workers){
if(thread.joinable())thread.join();
}
workers.clear();
totalthreadcount=0;
idlethreadcount=0;
}
void worker(){
auto lastworktime=chrono::steady_clock::now();
while(isrunning){
task t;
bool hastask=false;
if(taskqueue.trytake(t)){
hastask=true;
}
else{
idlethreadcount++;
bool timeout=taskqueue.notaskforseconds(keepalivetime);
idlethreadcount--;
if(!timeout&&taskqueue.take(t)){
hastask=true;
}
}
if(hastask){
t();
lastworktime=chrono::steady_clock::now();
}
else{
auto now=chrono::steady_clock::now();
auto idleduration=chrono::duration_cast<chrono::seconds>(now-lastworktime);
if( idleduration.count() >= keepalivetime && totalthreadcount > corethreadsize){
totalthreadcount--;
return;
}
}
}
}
public:
cachedthreadpool(int coresize=4,int maxsize=20,int keepaliveseconds=60,int queuesize=100)
:corethreadsize(coresize),
maxthreadsize(maxsize),
keepalivetime(keepaliveseconds),
taskqueue(queuesize),
idlethreadcount(0),
totalthreadcount(0),
isrunning(true){
if(coresize<=0)coresize=1;
if(maxsize<coresize)maxsize=coresize*2;
for(int i=0;i<corethreadsize;i++){
create_newthread();
}
cout<< "[CachedThreadPool] 启动: "
<< "核心" << corethreadsize << "线程, "
<< "最大" << maxthreadsize << "线程, "
<< "空闲超时" << keepalivetime << "秒"
<< endl;
}
//typename... args:定义一个类型参数包,args可以代表 0 个、1 个或多个不同的类型。
//args&&...a:定义一个值参数包,a可以代表 0 个,1 个或多个对应类型的参数(这里用&&是万能引用,支持左值/右值传递)
template<typename func,typename...args>
void execute(func&&f,args&&...a){//提交任务 无返回值
if(!isrunning){
throw runtime_error("线程池已停止");
}
auto task=bind(forward<func>(func),forward<args>(args)...);//:绑定函数和参数,forward保持参数的左值/右值属性
if(idlethreadcount==0&&totalthreadcount<maxthreadsize){
create_newthread();
}
taskqueue.put(task);
}
template<typename func,typename...args>//提交任务 有返回值
auto submit(func&&f,args&&...a)->future<decltype(func(a...))>{//// 返回值:future<返回值类型>,用->指定返回值(尾置返回类型,因为要推导返回值)
using returntype=decltype(func(a...));// 推导任务函数的返回值类型,给类型起别名returntype
// 1. packaged_task:把函数包装成"可异步执行的任务"
// 模板参数returntype()表示"无参数、返回returntype的可调用对象"
// bind把函数f和参数a...绑定,变成无参数的可调用对象
packaged_task<returntype()>task(bind(forward<func>(f),forward<args>(a)...));
future<returntype>result=task.get_future();// 2. 获取这个任务的future对象(提货单),后续用它拿返回值
if(idlethreadcount==0&&totalthreadcount<maxthreadsize){// 3. 线程池逻辑:没空闲线程且没到上限,就创建新线程
create_newthread();
}
// 4. 把任务放入队列:用lambda封装,move转移task所有权(避免拷贝)
//mutable:允许lambda内部修改捕获的task(因为task()是执行任务,需要修改状态)
taskqueue.put([task==move(task)](mutable){task();});// // 执行任务,结果会自动存到对应的future里
return result;
}
//packaged_task 和 future 是一对 "搭档"
// packaged_task:负责包装任务,把函数和 "结果存储" 绑定在一起。
// future:负责获取结果,是packaged_task的 "结果出口"。
// 关系:packaged_task执行时,会把返回值存起来,你通过future.get()就能拿到这个值(如果任务没执行完,get()会阻塞,直到拿到结果)。
~cachedthreadpool(){
stop();
}
void stop(){
call_once(isstop,[this]{stop_allthreads();cout<<"pool停止"<<endl;});
}
int getldlecount()const{
return idlethreadcount;
}
int gettotalcount()const{
return totalthreadcount;
}
size_t getqueuesize()const{
return taskqueue.size();
}
bool isrunning()const{
return isrunning;
}
};
#endif
动态线程管理流程:
submit任务 → idleThreadCount == 0? → 是 → 创建新线程
↓ 否
使用空闲线程
线程空闲 → 超过keepAliveTime? → 是 → 销毁线程
↓ 否
保留线程
FixedThreadPool与CachedThreadPool特性对比
| 特性 | FixedThreadPool | CachedThreadPool |
|---|---|---|
| 重用 | FixedThreadPool与CacheThreadPool差不多,也是能 reuse 就用,但不能随时建新的线程 | 缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse;如果没有,就建一个新的线程加入池中 |
| 池大小 | 可指定nThreads,固定数量 | 可增长,最大值 Integer.MAX_VALUE |
| 队列大小 | 无限制 | 无限制 |
| 超时 | 无IDLE | 默认60秒 IDLE |
| 使用场景 | 所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器。定长线程池适用于执行负载重,cpu使用频率高的任务;这个主要是为了防止太多线程进行大量的线程频繁切换,得不偿失。 | 大量短生命周期的异步任务。适用于执行大量并发短期异步的任务;注意,任务量的负载要轻。 |
| 结束 | 不会自动销毁 | 注意,放入 CachedThreadPool 的线程不必担心其结束,超过 TIMEOUT 不活动,其余自动被终止。 |
最佳实践
FixedThreadPool和CachedThreadPool两者对高负载的应用都不是特别友好。
CachedThreadPool要比FixedThreadPool危险很多。
如果应用要求高负载、低延迟,最好不要选择以上两种线程池:
-
任务队列的无边界:会导致内存溢出以及高延迟
-
长时间运行会导致CachedThreadPool在线程创建上失控
因为两者都不是特别友好,所以推荐使用 ThreadPoolExecutor,它提供了很多参数可以进行细粒度的控制。
-
将任务队列设置成有边界的队列
-
使用合适的 RejectionHandler 拒绝处理程序。
-
如果在任务完成前需要执行某些操作,可以重载
-
beforeExecute(Thread, Runnable)
-
afterExecute(Runnable, Throwable)
-
-
重载 ThreadFactory,如果有线程定制化的需求
-
在运行时动态控制线程池的大小(Dynamic Thread Pool)。
使用场景
适用于以下场景:
-
大量短期任务:CachedThreadPool适合处理大量的短期任务,当任务到来时会尽可能地创建新线程来执行任务,如果有空闲的线程可用则会重复利用现有线程,而不会让线程闲置。这样可以避免因为频繁创建线程和销毁线程所带来的额外开销。
-
任务响应快速:CachedThreadPool适合处理需要快速响应的任务,因为它可以根据任务的到来快速创建启动新线程来执行任务,从而减少任务等待时间。
-
不需要限制线程数量:CachedThreadPool适合在任务到来时不限制线程数量的情况下处理任务。它的最大线程数是不限制的,只要内存空间足够,可以根据任务的到来动态创建新线程。
-
短期性任务的高并发性:由于CachedThreadPool可以根据需要动态地创建线程,所以适合处理需要高并发性的短期性任务。当任务处理完毕后,线程池会保持一定的空闲线程用于下一批任务的到来。
需要注意的是,CachedThreadPool的线程数量是不受限制的,如果任务过多可能会导致线程数量过多而造成系统资源过度消耗,因此在使用时需要根据实际情况灵活调整线程数量或使用其他类型的线程池来控制资源的使用。