一、概述
动态调整线程数量:与固定式线程池不同,缓存式线程池的线程数量是动态调整的。当有新任务提交时,如果线程池中有空闲的线程,则会立即使用空闲线程执行任务;如果线程池中没有空闲线程,则会创建一个新的线程来执行任务。当线程空闲一段时间后,超过线程最大空闲时间,线程将会被回收和销毁。
二、同步队列的设计
对于缓存式线程池同步队列的设计,首先会在类中设置容器来存放任务(list),另外使用mutex来实现生产者和消费者的互斥关系,以及两个条件变量(生产者条件变量cv_x,消费者条件变量cv_x)来唤醒两者。需要设计m_waitTime来限制任务队列满等待时间,以及m_maxSize来限制任务队列最大任务数量,防止任务数过多撑满内存。最后,需要一个布尔类型变量m_needStop来设置同步队列启动与否。
在设置生产者和消费者模型时(Add和Take),需要设置条件变量的等待时间,当到达m_waitTime,说明出现超时,此时便可以直接退出,防止线程长期占用CPU;在强制停止同步队列时,即Stop()(还有等待停止-->等同步队列里的值都进行完了才进行停止WaitStop()),首先需要进行加锁,不能让生产者和消费者此时再去获取锁,设置关闭同步队列标志m_stop,唤醒生产者和消费者,此时生产者和消费者就会退出。
#ifndef SYNCQUEUE3_HPP//等待同步队列
#define SYNCQUEUE3_HPP
#include<iostream>
#include<list>
#include<mutex>;
#include<condition_variable>
#include<chrono>
using namespace std;
template<class Task>
class SyncQueue
{
private:
std::list<Task>m_queue;
mutable std::mutex mux;
std::condition_variable cv_x;//消费者
std::condition_variable cv_s;//生产者
std::condition_variable m_waitStop;//等待停止
size_t maxsize;//任务上限
bool m_stop;//false-->没停止(判断同步队列是否停止)
size_t m_waitTime = 10;//10ms的等待时间
bool IsFull()const { return m_queue.size() >= maxsize; }
bool IsEmpty()const { return m_queue.empty(); }
template<class F>
int Add(F&& f)//添加到生产者
{
std::unique_lock<std::mutex>lock(mux);
while (!m_stop && IsFull())
{
//cv_s.wait(lock);
if (std::cv_status::timeout == cv_s.wait_for(lock, std::chrono::milliseconds(m_waitTime)))
{
return -1;
}
}
//cv_s.wait(lock, [this]()->bool {return !m_stop && !IsFull() });
if (m_stop)//同步队列停止
{
return -2;
}
m_queue.push_back(std::forward<F>(f));
cv_x.notify_all();
return 0;
}
public:
SyncQueue(size_t max) :maxsize(max), m_stop(false) {}
~SyncQueue()
{
}
void Stop()//强制停止-->不管同步队列有没有值都进行停止
{
{
std::unique_lock<std::mutex>lock(mux);
m_stop = true;
}
cv_x.notify_all();
cv_s.notify_all();
}
void WaitStop()//等待停止-->等同步队列里的值都进行完了才进行停止
{
std::unique_lock<std::mutex>lock(mux);
while (!IsEmpty())
{
//m_waitStop.wait(lock);
m_waitStop.wait_for(lock, std::chrono::milliseconds(1));//等待1ms,超过1ms后自己就启动
}
m_stop = true;
cv_x.notify_all();
cv_s.notify_all();
}
int Put(Task&& task)//添加任务(生产者)
{
return Add(std::forward<Task>(task));
}
int Put(const Task& task)//添加任务(生产者)
{
return Add(task);
}
int Take(std::list<Task>& val)//消费者-->一次消费所有
{
std::unique_lock<std::mutex>lock(mux);
while (!m_stop && IsEmpty())
{
//cv_x.wait(lock);
if (std::cv_status::timeout == cv_x.wait_for(lock, std::chrono::seconds(1)))//看是否超时
{
return -1;
}
}
if (m_stop)
{
return -2;
}
val = std::move(m_queue);
cv_s.notify_all();
return 0;
}
int Take(Task& val)//消费者-->一次消费一个
{
std::unique_lock<std::mutex>lock(mux);
while (!m_stop && IsEmpty())
{
//cv_x.wait(lock);
if (std::cv_status::timeout == cv_x.wait_for(lock, std::chrono::seconds(1)))//看是否超时
{
return -1;
}
}
if (m_stop)
{
return -2;
}
val = m_queue.front();
m_queue.pop_front();
cv_s.notify_all();
return 0;
}
bool Empty()const//判空
{
std::unique_lock<std::mutex>lock(mux);
return m_queue.empty();
}
bool Full()const //判满
{
std::unique_lock<std::mutex>lock(mux);
return m_queue.size() >= maxsize;
}
size_t Size()const //队列中元素个数
{
//std::unique_lock<std::mutex>lock(mux);
return m_queue.size();
}
size_t Count()const //队列中元素个数
{
return m_queue.size();
}
};
#endif
三、缓存式线程池的设计
对于缓存式线程池的设计,需要设置变量m_maxIdleTime线程最大存活时间,当线程获取不到任务时或者同步队列为空,线程的最大存活时间超过m_maxIdleTime时,就会释放该空闲线程。
注意可调用对象包装器std::function和打包器std::bind的使用,通常使用这两种特性来作为生产者添加任务。using Task=std::function<void(void)>
在类中设置std::map<std::thread::id, std::unique_ptr<std::thread>>m_threadgroup;来作为线程组管理线程。其中,m_minThread->核心线程数量,也作为缓存式线程池线程数下限,m_maxThread->上限,最大线程数量,对于最大线程数量可以通过hardware_concurrency()(这个会计算出电脑的最大逻辑处理器数量)来设置系统支持并发数量线程近似值。另外,还会有m_idleThreadSize->空闲线程数量,m_curThreadSize->当前线程池中线程总数量。
实现如下:
#pragma once
#include"SyncQueue3_CachedThread.hpp"//缓存式线程池
#include<iostream>
#include<atomic>
#include<functional>
#include<map>
#include<thread>
#include<chrono>
#include<mutex>
#include<future>
#include<condition_variable>
using namespace std;
using namespace std::chrono;
class CachedThreadPool
{
public:
using Task = std::function<void(void)>;
private:
SyncQueue<Task>m_queue;
std::map<std::thread::id, std::unique_ptr<std::thread>>m_threadgroup;//存放线程,根据线程id号找到相应线程
std::mutex m_mux;//互斥锁-->防止添加线程和结束线程同时进行
std::condition_variable m_threadExit;//条件变量,用于停止线程
std::atomic_int m_maxIdleTime = 5;//最大超时时间60s
std::atomic_int m_maxThread = std::thread::hardware_concurrency() + 1;//线程池数量的上限,获取当前处理器的内核数
std::atomic_int m_minThread = 2;//线程池数量的下限
std::atomic<int> m_curThreadSize = 0;//当前线程的个数
std::atomic<int> m_idleThreadSize = 0;//空闲线程数
std::atomic_bool m_running = false;//线程池是否运行
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_unique<std::thread>(&CachedThreadPool::RunningThread, this);
auto tha = std::unique_ptr<std::thread>(new std::thread(&CachedThreadPool::RunningThread, this));//获取线程对象
std::thread::id tid = tha->get_id();//等价于auto tid=tha->get_id();
m_threadgroup.emplace(tid, std::move(tha));
++m_idleThreadSize;
}
}
void RunningThread()//线程的入口函数
{
auto tid = std::this_thread::get_id();//获取id
auto startTime = std::chrono::high_resolution_clock::now();//得到现有时间
while (m_running)
{
Task task;
if (m_queue.Size() == 0)//队列是否为0
{
auto now = std::chrono::high_resolution_clock::now();
auto intervalTime = duration_cast<std::chrono::seconds>(now - startTime).count();//得到时间差并转换为秒
std::unique_lock<std::mutex>locker(m_mux);
if (intervalTime >= m_maxIdleTime && m_curThreadSize > m_minThread)
{
cout << "m_threadgroup.find(tid)->second->detach()======" << endl;
cout << "线程id为------" << tid << endl;
for (auto& tha : m_threadgroup)
{
cout << "线程id为:" << tha.first << endl;
}
m_threadgroup.find(tid)->second->detach();//找到id号对应的值并将其分离出去datack()分离
m_threadgroup.erase(tid);
--m_curThreadSize;
--m_idleThreadSize;
cout << "入口空闲线程数:" << m_idleThreadSize << endl;
cout << "入口当前线程个数:" << m_curThreadSize << endl;
m_threadExit.notify_one();
return;
}
}
if (!m_queue.Take(task))
{
--m_idleThreadSize;
task();//调用函数
++m_idleThreadSize;
startTime = std::chrono::high_resolution_clock::now();//改变最初的开始时间
}
}
}
void stopThread()//停止线程
{
m_queue.WaitStop();
m_minThread = 0;//下限
m_maxIdleTime = 1;//最大超时时间
std::unique_lock<std::mutex>locker(m_mux);
while (!m_threadgroup.empty())
{
m_threadExit.wait_for(locker, std::chrono::milliseconds(1));//等待1ms
//1)弃锁 2)阻塞 3)被唤醒 4)获得锁
}
m_running = false;
}
void Addnewthread()//满足条件添加新的线程
{
std::lock_guard<std::mutex>locker(m_mux);
if (m_idleThreadSize <= 0 && m_curThreadSize < m_maxThread)
{
//创建新的线程
auto tha = std::unique_ptr<std::thread>(new std::thread(&CachedThreadPool::RunningThread, this));//获取线程对象
std::thread::id tid = tha->get_id();//等价于auto tid=tha->get_id();
m_threadgroup.emplace(tid, std::move(tha));
++m_idleThreadSize;
++m_curThreadSize;
cout << "新添加的线程id为:" << tid << endl;
cout << "添加新的线程-------------------" << endl;
cout << "空闲线程数:" << m_idleThreadSize << endl;
cout << "当前线程个数:" << m_curThreadSize << endl;
}
}
public:
CachedThreadPool(int numthreads=2, int qusize=200):m_queue(qusize)
{
Start(numthreads);
}
~CachedThreadPool()
{
stop();
}
void stop()//结束任务
{
std::call_once(m_flag, [this]() {stopThread(); });
}
template<class Func, class ... Args>//可变模板参
auto AddTask(Func&& func, Args&& ... args)//添加任务,带有返回值
{
using RetType = decltype(func(args...));//获取函数的返回值
auto task = std::make_shared<std::packaged_task<RetType()>>(
std::bind(std::forward<Func>(func), std::forward<Args>(args)...)
);
std::future<RetType>result = task->get_future();
if (m_queue.Put([task] {(*task)(); }) != 0)//拒绝策略
{
cout << "not add........" << endl;
(*task)();
}
Addnewthread();
return result;
}
void excute(const Task& task)
{
if (m_queue.Put(task) != 0)//拒绝策略
{
cout << "not add........" << endl;
task();
}
Addnewthread();
}
void excute(Task&& task)
{
if (m_queue.Put(std::forward<Task>(task)) != 0)
{
cout << "not add........" << endl;
task();
}
Addnewthread();
}
};
四、固定式线程池和缓存式线程池比较
固定式线程池与缓存式线程池差不多,也是能使用就使用,但不能随时创建新的线程。可指定固定数量。固定式线程池多数针对一些很稳定很固定的正规并发线程,多用于服务器。定长线程池,适用于执行负载重,cpu使用频率高的任务,这个主要是为了防止太多线程进行大量的线程切换。
缓存式线程池先查看线程池中有无空闲线程,如果有就使用,如果没有,就创建一个新的线程加入池子中。线程池线程数目存在下限和上限。空闲线程有最大存活时间,放入缓存式线程池的线程不必担心其结束,超过timeout时间不活动,会被终止。适用于执行大量并发短期异步的任务;注意,任务量的负载要轻。
五、适用场景
1、大量短期任务:缓存式线程池适合处理大量的短期任务,当任务到来时尽可能地创建新线程来执行任务,如果有空闲的线程可用则会重复利用现有线程,而不会让线程闲置。这样可以避免因为创建线程频繁和销毁线程带来的额外开销。
2、任务响应快速:缓存式线程池适合处理需要快速相应的任务,因为它可以根据任务的到来快速创建和启动新线程来执行新任务,从而减少任务等待时间。
3、不需要限制线程数量:只要内存空间够,可以根据任务地到来动态创建新的线程。
4、短期性任务的高并发性:缓存式线程池可以根据需要动态地创建线程,所以适合处理需要高并发性的短期性任务。当任务处理完毕后,线程池会保持一定的空闲线程用于下一批任务的到来。