packaged_task和future机制
packaged_task
类似于函数对象 function<T>
背景:让提交任务更加方便,我们希望能达到如下形式提交任务
int sum1(int a, int b)
{
return a + b;
}
int sum2(int a, int b, int c)
{
return a + b + c;
}
int main()
{
ThreadPool pool;
pool.submitTask(sum1, 10, 20);
pool.submitTask(sum2, 10, 20, 30);
return 0;
}
使用案例
#include <iostream>
#include <functional>
#include <thread>
#include <future> //
#include <chrono>
using namespace std;
int sum1(int a, int b)
{
return a + b;
}
int sum2(int a, int b, int c)
{
return a + b + c;
}
int main()
{
// 将 sum1 包装成一个任务对象
packaged_task<int(int, int)> task(sum1);
future<int> res = task.get_future(); // future相当于上个版本的Result类
//task(10, 20);
// packaged_task已禁用拷贝构造和拷贝赋值
thread(std::move(task), 10, 20); // 放在线程中执行任务
cout << res.get() << endl; // 任务没执行完会阻塞
return 0;
}
重写线程池
submitTask的改变
使用了可变参模板,和尾置返回类型,重点在于任务的封装
// 函数的返回值类型通过尾部的 std::future<decltype(func(args...)>推断出来一个future类型
template<typename Func, typename... Args>
auto submitTask(Func&& func, Args&&... args) -> std::future<decltype(func(args...))>
{
// 打包任务,放入任务队列里面
using RType = decltype(func(args...));
auto task = std::make_shared<std::packaged_task<RType()>>(
std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
std::future<RType> result = task->get_future();
// 获取锁
std::unique_lock<std::mutex> lock(taskQueMtx_);
// 用户提交任务,最长不能阻塞超过1s,否则判断提交任务失败,返回
if (!notFull_.wait_for(lock, std::chrono::seconds(1),
[&]()->bool { return taskQue_.size() < (size_t)taskQueMaxThreshHold_; }))
{
// 表示notFull_等待1s种,条件依然没有满足
std::cerr << "task queue is full, submit task fail." << std::endl;
auto task = std::make_shared<std::packaged_task<RType()>>(
[]()->RType { return RType(); });
// 添加失败的默认任务对象要执行,不然外面调用future::get()永远得不到结果
// vs能检测task是否有执行,没有执行会出异常
(*task)();
return task->get_future();
}
// 如果有空余,把任务放入任务队列中
// taskQue_.emplace(sp);
// using Task = std::function<void()>;
taskQue_.emplace([task]() {(*task)(); });
taskSize_++;
// 因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务
notEmpty_.notify_all();
// cached模式 任务处理比较紧急 场景:小而快的任务 需要根据任务数量和空闲线程的数量,判断是否需要创建新的线程出来
if (poolMode_ == PoolMode::MODE_CACHED
&& taskSize_ > idleThreadSize_
&& curThreadSize_ < threadSizeThreshHold_)
{
std::cout << ">>> create new thread..." << std::endl;
// 创建新的线程对象
auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
int threadId = ptr->getId();
threads_.emplace(threadId, std::move(ptr));
// 启动线程
threads_[threadId]->start();
// 修改线程个数相关的变量
curThreadSize_++;
idleThreadSize_++;
}
// 返回任务的Result对象
return result;
}
完整代码
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <iostream>
#include <vector>
#include <queue>
#include <memory>
#include <atomic>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <unordered_map>
#include <functional>
#include <future>
const int TASK_MAX_THRESHHOLD = INT32_MAX;
const int THREAD_MAX_THRESHHOLD = 1024;
const int THREAD_MAX_IDLE_TIME = 60; // 线程最大空闲时间,超过则回收
enum class PoolMode // C++11
{
MODE_FIXED, // 固定数量的线程池
MODE_CACHED, // 线程池数量可变的线程池
};
class Thread
{
public:
using ThreadFunc = std::function<void(int)>;
Thread(ThreadFunc func)
: func_(func)
, threadId_(generateId_++)
{
}
~Thread() = default;
// 启动线程
void start()
{
// 创建一个线程来执行一个线程函数
std::thread t(func_, threadId_);
t.detach(); // 设置分离线程,t线程自己控制自己生命周期
}
int getId() const
{
return threadId_;
}
private:
ThreadFunc func_;
static int generateId_;
int threadId_; // 保存线程对象的id,不是指系统给的线程号
};
int Thread::generateId_ = 0;
class ThreadPool
{
public:
ThreadPool()
: initThreadSize_(4)
, taskSize_(0)
, idleThreadSize_(0)
, curThreadSize_(0)
, taskQueMaxThreshHold_(TASK_MAX_THRESHHOLD)
, threadSizeThreshHold_(THREAD_MAX_THRESHHOLD)
, poolMode_(PoolMode::MODE_FIXED)
, isPoolRunning_(false)
{
}
~ThreadPool()
{
isPoolRunning_ = false;
// 等待线程池里所有线程返回,两种状态:阻塞 或 正在执行任务中
std::unique_lock<std::mutex> lock(taskQueMtx_);
notEmpty_.notify_all(); // 通知所有消费者,让它们检测到运行状态为false可以退出了
// 等待线程销毁
exitCond_.wait(lock, [&]()->bool { return threads_.size() == 0; });
}
// 设置线程池工作模式
void setMode(PoolMode mode)
{
if (checkRunningState())
return;
poolMode_ = mode;
}
// 设置task任务队列上限阈值
void setTaskQueMaxThreadhold(int threshhold)
{
if (checkRunningState())
return;
taskQueMaxThreshHold_ = threshhold;
}
// 设置线程池cached模式下线程的上限阈值
void setThreadSizeThreadhold(int threshhold)
{
if (checkRunningState())
return;
if (poolMode_ == PoolMode::MODE_CACHED)
{
threadSizeThreshHold_ = threshhold;
}
}
// 给线程池提交任务
template<typename Func, typename... Args>
auto submitTask(Func&& func, Args&&... args) -> std::future<decltype(func(args...))>
{
// 打包任务,放入任务队列里面
using RType = decltype(func(args...));
auto task = std::make_shared<std::packaged_task<RType()>>(
std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
std::future<RType> result = task->get_future();
// 获取锁
std::unique_lock<std::mutex> lock(taskQueMtx_);
// 用户提交任务,最长不能阻塞超过1s,否则判断提交任务失败,返回
if (!notFull_.wait_for(lock, std::chrono::seconds(1),
[&]()->bool { return taskQue_.size() < (size_t)taskQueMaxThreshHold_; }))
{
// 表示notFull_等待1s种,条件依然没有满足
std::cerr << "task queue is full, submit task fail." << std::endl;
auto task = std::make_shared<std::packaged_task<RType()>>(
[]()->RType { return RType(); });
(*task)();
return task->get_future();
}
// 如果有空余,把任务放入任务队列中
// taskQue_.emplace(sp);
// using Task = std::function<void()>;
taskQue_.emplace([task]() {(*task)(); });
taskSize_++;
// 因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务
notEmpty_.notify_all();
// cached模式 任务处理比较紧急 场景:小而快的任务 需要根据任务数量和空闲线程的数量,判断是否需要创建新的线程出来
if (poolMode_ == PoolMode::MODE_CACHED
&& taskSize_ > idleThreadSize_
&& curThreadSize_ < threadSizeThreshHold_)
{
std::cout << ">>> create new thread..." << std::endl;
// 创建新的线程对象
auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
int threadId = ptr->getId();
threads_.emplace(threadId, std::move(ptr));
// 启动线程
threads_[threadId]->start();
// 修改线程个数相关的变量
curThreadSize_++;
idleThreadSize_++;
}
// 返回任务的Result对象
return result;
}
// 开启线程池
void start(int initThreadSize = std::thread::hardware_concurrency())
{
// 设置线程池运行状态
isPoolRunning_ = true;
// 记录初始线程个数
initThreadSize_ = initThreadSize;
curThreadSize_ = initThreadSize;
// 创建线程对象
// 把线程函数给到thread线程对象
for (size_t i = 0; i < initThreadSize_; i++)
{
auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
int threadId = ptr->getId();
// unique_ptr的拷贝构造和赋值都被delete,所以要把ptr转为右值
threads_.emplace(threadId, std::move(ptr));
}
// 启动所有线程
for (size_t i = 0; i < initThreadSize_; i++)
{
threads_[i]->start(); // 需要去执行一个线程函数
idleThreadSize_++; // 空闲线程数+1(cached模式)
}
}
// 禁止拷贝构造和赋值
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
private:
// 定义线程函数
void threadFunc(int threadid)
{
auto lastTime = std::chrono::high_resolution_clock().now();
// 所有任务必须执行完成,线程池才可以回收所有线程资源
for (;;)
{
Task task;
{
// 先获取锁
std::unique_lock<std::mutex> lock(taskQueMtx_);
std::cout << "tid:" << std::this_thread::get_id()
<< "尝试获取任务..." << std::endl;
// cached模式下,有可能已经创建了很多的线程,但是空闲时间超过60s,应该把多余的线程
// 结束回收掉(超过initThreadSize_数量的线程要进行回收)
// 当前时间 - 上一次线程执行的时间 > 60s
// 每一秒中返回一次 怎么区分:超时返回?还是有任务待执行返回
// 锁 + 双重判断
while (taskQue_.size() == 0)
{
// 线程池要结束,回收线程资源
if (!isPoolRunning_)
{
threads_.erase(threadid); // std::this_thread::getid()
std::cout << "threadid:" << std::this_thread::get_id() << " exit!"
<< std::endl;
exitCond_.notify_all();
return; // 线程函数结束,线程结束
}
if (poolMode_ == PoolMode::MODE_CACHED)
{
// 条件变量,超时返回了
if (std::cv_status::timeout ==
notEmpty_.wait_for(lock, std::chrono::seconds(1)))
{
auto now = std::chrono::high_resolution_clock().now();
auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime);
if (dur.count() >= THREAD_MAX_IDLE_TIME
&& curThreadSize_ > initThreadSize_)
{
// 开始回收当前线程
// 记录线程数量的相关变量的值修改
// 把线程对象从线程列表容器中删除 没有办法 threadFunc《=》thread对象
// threadid => thread对象 => 删除
threads_.erase(threadid); // std::this_thread::getid()
curThreadSize_--;
idleThreadSize_--;
std::cout << "threadid:" << std::this_thread::get_id() << " exit!"
<< std::endl;
return;
}
}
}
else
{
// 等待notEmpty条件
notEmpty_.wait(lock);
}
}
idleThreadSize_--;
std::cout << "tid:" << std::this_thread::get_id()
<< "获取任务成功..." << std::endl;
// 从任务队列种取一个任务出来
task = taskQue_.front();
taskQue_.pop();
taskSize_--;
// 如果依然有剩余任务,继续通知其它得线程执行任务
if (taskQue_.size() > 0)
{
notEmpty_.notify_all();
}
// 取出一个任务,进行通知,通知可以继续提交生产任务
notFull_.notify_all();
} // 就应该把锁释放掉
// 当前线程负责执行这个任务
if (task != nullptr)
{
task(); // 执行function<void()>
}
idleThreadSize_++;
lastTime = std::chrono::high_resolution_clock().now(); // 更新线程执行完任务的时间
}
}
// 检查pool的运行状态
bool checkRunningState() const
{
return isPoolRunning_;
}
private:
//std::vector<std::unique_ptr<Thread>> threads_; // 线程列表
// 建立线程的id和线程对象指针的映射,方便查询
std::unordered_map<int, std::unique_ptr<Thread>> threads_; // 线程列表
size_t initThreadSize_; //初始线程数量
int threadSizeThreshHold_; // 线程数量上限(cached模式使用)
std::atomic_int curThreadSize_; // 记录当前线程池里面线程的总数量(cached模式使用)
std::atomic_int idleThreadSize_; // 记录空闲线程的数量(cached模式使用)
// 任务队列
using Task = std::function<void()>;
std::queue<Task> taskQue_; // 任务队列
std::atomic_int taskSize_; // 任务的数量 ?
int taskQueMaxThreshHold_; // 任务队列数量上限阈值
std::mutex taskQueMtx_; // 保证任务队列的线程安全
std::condition_variable notFull_; // 表示任务队列不满
std::condition_variable notEmpty_; // 表示任务队列不空
std::condition_variable exitCond_; // 等到线程资源全部回收
PoolMode poolMode_; // 线程池模式
std::atomic_bool isPoolRunning_; // 线程池运行状态
};
#endif
测试1
int sum1(int a, int b)
{
return a + b;
}
int sum2(int a, int b, int c)
{
return a + b + c;
}
int main()
{
ThreadPool pool;
pool.start(4);
future<int> r1 = pool.submitTask(sum1, 10, 20);
future<int> r2 = pool.submitTask(sum2, 10, 20, 30);
future<int> r3 = pool.submitTask([](int b, int e)->int {
int sum = 0;
for (int i = b; i <= e; i++) sum += i;
return sum;
}, 1, 100);
//future<int> r1 = pool.submitTask(sum1, 10, 20);
cout << r1.get() << endl;
cout << r2.get() << endl;
cout << r3.get() << endl;
return 0;
}
实验结果
测试2
int main()
{
ThreadPool pool;
pool.setMode(PoolMode::MODE_CACHED);
pool.start(2);
future<int> r1 = pool.submitTask(sum1, 10, 20);
future<int> r2 = pool.submitTask(sum2, 10, 20, 30);
future<int> r3 = pool.submitTask([](int b, int e)->int {
int sum = 0;
for (int i = b; i <= e; i++) sum += i;
return sum;
}, 1, 100);
//future<int> r1 = pool.submitTask(sum1, 10, 20);
cout << r1.get() << endl;
cout << r2.get() << endl;
cout << r3.get() << endl;
return 0;
}
测试3
将TASK_MAX_THRESHHOLD
设置为 2,提交6个任务(其中3个睡眠2s),线程池只有两个线程。
int sum1(int a, int b)
{
this_thread::sleep_for(chrono::seconds(2));
return a + b;
}
int sum2(int a, int b, int c)
{
this_thread::sleep_for(chrono::seconds(2));
return a + b + c;
}
int main()
{
ThreadPool pool;
//pool.setMode(PoolMode::MODE_CACHED);
pool.start(2);
future<int> r1 = pool.submitTask(sum1, 10, 20);
future<int> r2 = pool.submitTask(sum2, 10, 20, 30);
future<int> r3 = pool.submitTask([](int b, int e)->int {
int sum = 0;
for (int i = b; i <= e; i++) sum += i;
return sum;
}, 1, 100);
future<int> r4 = pool.submitTask([](int b, int e)->int {
int sum = 0;
for (int i = b; i <= e; i++) sum += i;
return sum;
}, 1, 100);
future<int> r5 = pool.submitTask([](int b, int e)->int {
int sum = 0;
for (int i = b; i <= e; i++) sum += i;
return sum;
}, 1, 100);
//future<int> r1 = pool.submitTask(sum1, 10, 20);
cout << r1.get() << endl;
cout << r2.get() << endl;
cout << r3.get() << endl;
cout << r4.get() << endl;
cout << r5.get() << endl;
return 0;
}
r1和r2放入队列,被两个线程取走,然后r3和r4再将任务放入队列(任务队列满),此时由于r1和r2任务中有sleep仍然被两个线程执行中,此时任务队列已满,主线程无法将r5放入任务队列。