文章目录
基于可变参模版使用户提交任务中任务执行函数返回值是任意类型
可变参模版的使用
定义线程函数 使用std::bind绑定函数给thread对象,调用构造
代码如下:
// 创建thread线程对象的时候,把线程函数给到thread线程对象
auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
返回值实现任意类型
用户写完任务执行函数返回值的类型不可知,在主线程里用户调用Result获取返回值时,如果任务线程未执行完需要阻塞住。在这里需要实现接收run函数返回值的Any类,其实在C++17中已经提供std::Any,形如python java中的Object
代码如下:
class Any
{
public:
Any() = default;
~Any() = default;
Any(const Any&) = delete;
Any& operator=(const Any&) = delete;
Any(Any&&) = default;
Any& operator=(Any&&) = default;
// 这个构造函数可以让Any类型接收任意其它的数据
template<typename T> // T:int Derive<int>
Any(T data) : base_(std::make_unique<Derive<T>>(data))
{}
// 这个方法能把Any对象里面存储的data数据提取出来
template<typename T>
T cast_()
{
// 我们怎么从base_找到它所指向的Derive对象,从它里面取出data成员变量
// 基类指针 =》 派生类指针 RTTI
Derive<T>* pd = dynamic_cast<Derive<T>*>(base_.get());
if (pd == nullptr)
{
throw "type is unmatch!";
}
return pd->data_;
}
private:
// 基类类型
class Base
{
public:
virtual ~Base() = default;
};
// 派生类类型
template<typename T>
class Derive : public Base
{
public:
Derive(T data) : data_(data)
{}
T data_; // 保存了任意的其它类型
};
private:
// 定义一个基类的指针
std::unique_ptr<Base> base_;
};
首先任意类型很容易想到template
第二一个类型 指向 其他任意类型 很容易想到基类指针指向派生类对象
因此Any的实现如下:
Any ⇒ Base* —>
Derive public Base
T data
注意基类的析构要实现成虚函数;
当基类指针指向派生类的时候,若基类析构函数不声明为虚函数,在析构时,只会调用基类而不会调用派生类的析构函数,从而导致内存泄露。
基于条件变量和互斥锁实现任务提交线程和执行线程间的通信机制
线程池用户提交任务是生产者,线程执行函数是消费者。
代码如下:
// 给线程池提交任务 用户调用该接口,传入任务对象,生产任务
Result ThreadPool::submitTask(std::shared_ptr<Task> sp)
{
// 获取锁
std::unique_lock<std::mutex> lock(taskQueMtx_);
...
// 因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务
notEmpty_.notify_all();
}
// 定义线程函数 线程池的所有线程从任务队列里面消费任务
void ThreadPool::threadFunc(int threadid) // 线程函数返回,相应的线程也就结束了
{
// 所有任务必须执行完成,线程池才可以回收所有线程资源
for (;;)
{
// 先获取锁
std::unique_lock<std::mutex> lock(taskQueMtx_);
...
// 如果依然有剩余任务,继续通知其它得线程执行任务 合理 既然我预定到了食物 我应该通知其他还在睡着的人去领食物 而不是等我吃完了食物过后再来领
// 这个地方其实还是有歧义 但是也无所谓 资本家要的结果是任务执行完 无关乎是谁完成多少 既然前面写了死循环 那这里通不通知都是一样 总会有人来领任务
if (taskQue_.size() > 0)
{
notEmpty_.notify_all();
}
// 取出一个任务,进行通知,通知可以继续提交生产任务
notFull_.notify_all();
}
}
核心思想:生产队生产出粮食会通知所有消费者来拿;消费者循环消费,只要有粮食就会抢锁,进入等待状态释放锁等待生产队召唤,消费完一份后会通知同伴们也来拿,并且召唤生产队你该生产了。
线程池的fixed和cached模式定制
fixed模式注意初始化与线程数量有关的变量,这些变量应该都是原子的,这样在多线程执行里会保证变量的正确性。
std::atomic_int curThreadSize_; // 记录当前线程池里面线程的总数量
std::atomic_int idleThreadSize_; // 记录空闲线程的数量
std::atomic_int taskSize_; // 任务的数量
cahced模式
当临时有大量任务提交时,应该创建更多的线程来获取任务,并且设置条件变量如果新增的线程空闲时长超过60秒应该销毁。
代码如下:
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);
}
future类型定制任务执行函数的返回值
C++11中引入标准库中的一些异步编程工具,例如std::future
在这里delctype推导函数返回值,packaged_task包装task用于获取返回值
// 给线程池提交任务
// 使用可变参模板编程,让submitTask可以接收任意任务函数和任意数量的参数
// pool.submitTask(sum1, 10, 20); csdn 大秦坑王 右值引用+引用折叠原理
// 返回值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(); });
(*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;
}