【项目复盘】基于可变参模版实现的线程池

基于可变参模版使用户提交任务中任务执行函数返回值是任意类型

可变参模版的使用

定义线程函数 使用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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值