目录
大伙看完实现觉得有帮助的话,给点个免费的Star呗,这是项目链接,拜托拜托。
1. CachedThreadPool简介
CachedThreadPool是一种可变大小的线程池,但是用户不可以手动调整线程池内的线程数量。该线程池特点如下:
- 在提交任务时,如无空闲线程则自动向线程池中添加新的线程。
- 线程池内的线程具有存活时间,如果在存活时间内没有取到任务,该线程就会退出。
- submit函数和stop函数的含义同FixedThreadPool,功能不再描述。可以去看上一篇博客的内容。
- 用户可以在构造函数中指定最小线程数量、最大线程数量以及线程存活时间。
本文的实现的关键思路如下:
- 使用map<id,thread>来存放线程,使用线程安全队列来存储即将退出的线程id
- 一个额外的cleaner线程,每当有一个线程退出时,就将该线程id存入线程安全队列中,cleaner线程就被唤醒,清理那些即将退出的线程。
- 每次提交任务时,检查空闲线程数量以及最大线程数,判断是否需要创建新的线程
剩余功能和FixedThreadPool类似,不再赘述。
2. CachedThreadPool源码
头文件如下
class CachedThreadPool {
public:
using Task = std::function<void()>;
CachedThreadPool(size_t corePoolSize = 0, size_t maxPoolSize = 1024, std::chrono::milliseconds keepAliveTime = std::chrono::milliseconds(1000));
virtual ~CachedThreadPool();
template<typename F, typename... Args>
auto submit(F&& f, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>>;
void stop(bool wait = true);
size_t core_workers_num()const;
size_t max_workers_num()const;
size_t workers_num()const;
size_t idle_workers_num()const;
size_t tasks_num()const{std::lock_guard<std::mutex> lock(taskMutex_);return tasks_.size();}
private:
std::chrono::milliseconds keepAliveTime_; // 线程存活时间
std::atomic<size_t> coreWorkersNum_; // 核心线程数
std::atomic<size_t> maxWorkersNum_; // 最大线程数
std::atomic<size_t> idleWorkersNum_; // 空闲线程数
mutable std::mutex workersMutex_;
std::unordered_map<std::thread::id, std::thread> workers_; // 用来存放线程
// 用于清理即将退出的线程
details::threadsafe_queue<std::thread::id> exitingWorkers_; // 存放要退出的线程id
std::thread exiting_worker_cleaner_; // 清理空闲线程的线程
std::mutex cleanerMutex_;
std::condition_variable cleaner_cond_;
std::vector<Task> tasks_; // 任务队列
mutable std::mutex taskMutex_;
std::condition_variable task_cond_;
std::atomic<bool> isTerminated_{ false };
std::atomic<bool> ifWait_{ true };
void worker_loop_();
void cleaner_loop_();
void check_and_add_worker_();
};
template<typename F, typename... Args>
auto CachedThreadPool::submit(F&& f, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>> {
using return_type = std::invoke_result_t<F, Args...>;
auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
check_and_add_worker_();
{
std::lock_guard<std::mutex> lock(taskMutex_);
tasks_.emplace_back([task]() { (*task)(); });
}
task_cond_.notify_one();
return res;
}
源文件如下:
#include "CachedThreadPool.h"
namespace ThreadPools {
size_t CachedThreadPool::core_workers_num() const { return coreWorkersNum_.load(); }
size_t CachedThreadPool::max_workers_num() const { return maxWorkersNum_.load(); }
size_t CachedThreadPool::workers_num() const {
std::lock_guard<std::mutex> lock(workersMutex_);
return workers_.size();
}
size_t CachedThreadPool::idle_workers_num() const { return idleWorkersNum_.load(); }
CachedThreadPool::CachedThreadPool(size_t corePoolSize, size_t maxPoolSize, std::chrono::milliseconds keepAliveTime) {
coreWorkersNum_.store(corePoolSize);
maxWorkersNum_.store(maxPoolSize);
keepAliveTime_ = keepAliveTime;
idleWorkersNum_.store(0);
for (size_t i = 0; i < coreWorkersNum_.load(); ++i) {
std::thread t(&CachedThreadPool::worker_loop_, this);
std::lock_guard<std::mutex> lock(workersMutex_);
workers_[t.get_id()] = std::move(t);
idleWorkersNum_.fetch_add(1);
}
exiting_worker_cleaner_ = std::thread(&CachedThreadPool::cleaner_loop_, this);
}
CachedThreadPool::~CachedThreadPool() {
stop(true);
}
void CachedThreadPool::stop(bool wait) {
ifWait_.store(wait);
isTerminated_.store(true);
task_cond_.notify_all();
for (auto& worker : workers_) {
if (worker.second.joinable()) {
worker.second.join();
}
}
if(exiting_worker_cleaner_.joinable())
exiting_worker_cleaner_.join();
}
void CachedThreadPool::worker_loop_() {
Task task;
while (true) {
{
std::unique_lock<std::mutex> lock(taskMutex_);
if (false == task_cond_.wait_for(lock, keepAliveTime_, [this]() { return isTerminated_.load() || !tasks_.empty(); })) {
// 如果超时,先判断线程数量是否小于最小值
std::lock_guard<std::mutex> lock(workersMutex_);
if (workers_.size() > coreWorkersNum_.load())
break;
else
continue;
}
// 如果线程池结束了
if (isTerminated_.load()) {
// 如果任务队列为空,则退出
// 如果不为空并且不想等待,则退出
if (tasks_.empty() || !ifWait_.load()) {
break;
}
}
// 能执行到这里,说明任务队列不为空
task = std::move(tasks_.back());
tasks_.pop_back();
}
idleWorkersNum_.fetch_sub(1);
task();
idleWorkersNum_.fetch_add(1);
}
// 线程退出时,将自己加入退出队列,等待cleaner线程处理
idleWorkersNum_.fetch_sub(1);
exitingWorkers_.push(std::this_thread::get_id());
cleaner_cond_.notify_one();
}
void CachedThreadPool::cleaner_loop_() {
std::thread t;
std::thread::id id;
while (true) {
{
std::unique_lock<std::mutex> lock(cleanerMutex_);
cleaner_cond_.wait(lock, [this]() { return isTerminated_.load() || !exitingWorkers_.empty(); });
if (isTerminated_.load()) {
return;
}
}
// 因为它是线程安全的,所以不需要加锁
exitingWorkers_.wait_and_pop(id);
{
std::lock_guard<std::mutex> lock(workersMutex_);
t = std::move(workers_[id]);
workers_.erase(id);
}
if (t.joinable())
t.join();
}
}
void CachedThreadPool::check_and_add_worker_() {
{
std::lock_guard<std::mutex> lock(workersMutex_);
if (idleWorkersNum_.load() > 0 || workers_.size() >= maxWorkersNum_.load())
{
return;
}
}
std::thread t(&CachedThreadPool::worker_loop_, this);
{
std::lock_guard<std::mutex> lock(workersMutex_);
workers_[t.get_id()] = std::move(t);
}
idleWorkersNum_.fetch_add(1);
}
} // namespace ThreadPools
下面是线程安全队列的代码:
#pragma once
#include <queue>
#include <mutex>
#include <condition_variable>
#include <type_traits>
namespace ThreadPools {
namespace details {
template<typename T>
class threadsafe_queue {
public:
threadsafe_queue() = default;
threadsafe_queue(const threadsafe_queue&) = delete;
threadsafe_queue& operator=(const threadsafe_queue&) = delete;
template<typename U, std::enable_if_t<std::is_convertible_v<U, T>, int> = 0>
void push(U&& item) {
std::lock_guard<std::mutex> lock(mtx_);
q_.push(std::forward<U>(item));
notEmpty_cond_.notify_one();
}
bool try_pop(T& item) {
std::lock_guard<std::mutex> lock(mtx_);
if (q_.empty()) {
return false;
}
item = std::move(q_.front());
q_.pop();
return true;
}
void wait_and_pop(T& item) {
std::unique_lock<std::mutex> lock(mtx_);
notEmpty_cond_.wait(lock, [this] { return !q_.empty(); });
item = std::move(q_.front());
q_.pop();
}
bool empty()const {
std::lock_guard<std::mutex> lock(mtx_);
return q_.empty();
}
size_t size()const {
std::lock_guard<std::mutex> lock(mtx_);
return q_.size();
}
private:
std::queue<T> q_;
mutable std::mutex mtx_;
std::condition_variable notEmpty_cond_;
};
} // namespace details
} // namespace ThreadPools
3. 测试代码
可以使用一下代码进行测试
#include <iostream>
#include <thread>
#include <cstdio>
#include "fixedThreadPool.h"
#include "CachedThreadPool.h"
using namespace std;
void task(int id) {
for (int i = 0; i < 10; i++)
{
printf("Task %d, threadid: %d, iteration %d\n", id, std::this_thread::get_id(), i);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
#define TASK_NUM 1000
int main()
{
ThreadPools::CachedThreadPool pool(4, 1024, std::chrono::milliseconds(10000));
for (int i = 0; i < TASK_NUM; i++) {
pool.submit(task, i);
}
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
while (true)
{
printf("-------tasks:%d, workers: %d, idle threads: %d ----------------\n",pool.tasks_num(), pool.workers_num(), pool.idle_workers_num());
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
大约二十多秒后的运行截图如下:
4. 后记
后面将会发布其他线程池的实现,敬请期待。如果有写的不太好的地方,敬请批评指正。