949-线程池项目版本一

threadpool.h

#ifndef THREADPOOL_H
#define THREADPOOL_H

#include <vector>
#include <queue>
#include <memory>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <unordered_map>

//Any类型:可以接收任意数据的类型
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_;
};

//实现一个信号量类
class Semaphore
{
public:
	Semaphore(int limit = 0) 
		:resLimit_(limit)
	{}
	~Semaphore() = default;

	//获取一个信号量资源
	void wait()
	{
		std::unique_lock<std::mutex> lock(mtx_);
		//等待信号量有资源,没有资源的话,会阻塞当前线程
		cond_.wait(lock, [&]()->bool {return resLimit_ > 0; });
		resLimit_--;
	}

	//增加一个信号量资源
	void post()
	{
		std::unique_lock<std::mutex> lock(mtx_);
		resLimit_++;
		//linux下condition_variable的析构函数什么也没做
		//导致这里状态已经失效,无故阻塞
		cond_.notify_all();//等待状态,释放mutex锁 通知条件变量wait的地方,可以起来干活了
	}
private:
	int resLimit_;
	std::mutex mtx_;
	std::condition_variable cond_;
};

//Task类型的前置声明
class Task;

//实现接收提交到线程池的task任务执行完成后的返回值类型Result
class Result
{
public:
	Result(std::shared_ptr<Task> task, bool isValid = true);
	~Result() = default;

	//问题一:setVal方法,获取任务执行完的返回值的
	void setVal(Any any);

	//问题二:get方法,用户调用这个方法获取task的返回值
	Any get();
private:
	Any any_;//存储任务的返回值
	Semaphore sem_;//线程通信信号量
	std::shared_ptr<Task> task_;//强智能指针指向对应获取返回值的任务对象 
	std::atomic_bool isValid_;//表示返回值是否有效
};

//任务抽象基类,给用户提供统一的处理方法,让用户可以传入各种类型的任务
class Task
{
public:
	Task();
	~Task() = default;
	void exec();
	void setResult(Result* res);

	//用户可以自定义任意任务类型,从Task继承,重写run方法,实现自定义任务处理
	virtual Any run() = 0;

private:
	Result* result_;//Result对象的生命周期 是大于 Task的,不能用强智能指针,引发交叉引用
};

//线程池支持的模式,C++新标准,访问这个枚举项的时候,要加上类型,杜绝枚举类型不同,枚举项名字相同产生的冲突问题
enum class PoolMode
{
	MODE_FIXED,//固定数量的线程
	MODE_CACHED,//线程数量可动态增长
};

//线程类型
class Thread
{
public:
	//线程函数对象类型
	using ThreadFunc = std::function<void(int)>;

	//线程构造
	Thread(ThreadFunc func);
	//线程析构
	~Thread();
	//启动线程
	void start();

	//获取线程id
	int getId()const;
private:
	ThreadFunc func_;
	static int generateId_;
	int threadId_;//保存线程id
};

/*
example:
ThreadPool pool;
pool.start(4);

class MyTask : public Task
{
	public:
		void run() { // 线程代码... }
};

pool.submitTask(std::make_shared<MyTask>());
*/

//线程池类型
class ThreadPool
{
public:
	//线程池构造
	ThreadPool();

	//线程池析构
	~ThreadPool();

	//设置线程池的工作模式
	void setMode(PoolMode mode);

	//设置task任务队列上线阈值,方便用户修改,因为有的用户的硬件配置高
	void setTaskQueMaxThreshHold(int threshhold);

	//设置线程池cached模式下线程阈值
	void setThreadSizeThreshHold(int threshhold);

	//给线程池提交任务
	Result submitTask(std::shared_ptr<Task> sp);

	//开启线程池
	void start(int initThreadSize = std::thread::hardware_concurrency());

	//禁止用户对线程池对象进行拷贝构造和赋值
	ThreadPool(const ThreadPool&) = delete;
	ThreadPool& operator=(const ThreadPool&) = delete;

private:
	//定义线程函数
	void threadFunc(int threadid);

	//检查pool的运行状态
	bool checkRunningState() const;

private:
	//std::vector<std::unique_ptr<Thread>> threads_;//线程列表
	std::unordered_map<int, std::unique_ptr<Thread>> threads_;//线程列表,线程id+线程对象

	int initThreadSize_;//初始的线程数量,可以用CPU的内核数来确定
	int threadSizeThreshHold_;//线程数量上限阈值
	std::atomic_int curThreadSize_;//记录当前线程池里面线程的总数量
	std::atomic_int idleThreadSize_;//记录空闲线程的数量

	std::queue<std::shared_ptr<Task>> taskQue_;//任务队列,1.得保证这个任务对象一直存在,2.通过基类指针指向派生类对象,产生多态
	std::atomic_int taskSize_;//任务的数量,通过CAS保证原子操作
	int taskQueMaxThreshHold_;//任务队列数量上限阈值,不会去修改它,不用考虑线程安全

	std::mutex taskQueMtx_;//保证任务队列的线程安全
	std::condition_variable notFull_;//表示任务队列不满,表示用户线程可以生产任务了
	std::condition_variable notEmpty_;//表示任务队列不空,线程池可以从任务队列取任务了
	std::condition_variable exitCond_;//等到线程资源全部回收

	PoolMode poolMode_;//当前线程池的工作模式
	std::atomic_bool isPoolRunning_;//表示当前线程池的启动状态,因为有可能在多个线程使用到,定义为CAS原子类型
};

threadpool.cc

#include "threadpool.h"

#include <functional>
#include <thread>
#include <iostream>

const int TASK_MAX_THRESHHOLD = INT32_MAX;
const int THREAD_MAX_THRESHHOLD = 1024;
const int THREAD_MAX_IDLE_TIME = 60;//单位:秒

//线程池构造
ThreadPool::ThreadPool()
	: initThreadSize_(0)
	, taskSize_(0)
	, idleThreadSize_(0)
	, curThreadSize_(0)
	, taskQueMaxThreshHold_(TASK_MAX_THRESHHOLD)
	, threadSizeThreshHold_(THREAD_MAX_THRESHHOLD)
	, poolMode_(PoolMode::MODE_FIXED)
	, isPoolRunning_(false)
{}

//线程池析构
ThreadPool::~ThreadPool()
{
	isPoolRunning_ = false;
	
	//等待线程池里面所有的线程返回  有两种状态:阻塞 & 正在执行任务中
	std::unique_lock<std::mutex> lock(taskQueMtx_);
	notEmpty_.notify_all();
	exitCond_.wait(lock, [&]()->bool {return threads_.size() == 0; });
}

//设置线程池的工作模式
void ThreadPool::setMode(PoolMode mode)
{
	if (checkRunningState())//不允许在启动后设置
		return;
	poolMode_ = mode;
}

//设置task任务队列上线阈值
void ThreadPool::setTaskQueMaxThreshHold(int threshhold)
{
	if (checkRunningState())
		return;
	taskQueMaxThreshHold_ = threshhold;
}

//设置线程池cached模式下的线程阈值
void ThreadPool::setThreadSizeThreshHold(int threshhold)
{
	if (checkRunningState())
		return;
	if (poolMode_ == PoolMode::MODE_CACHED)
	{
		threadSizeThreshHold_ = threshhold;
	}
}

//给线程池提交任务    用户调用该接口,传入任务对象,生产任务
Result ThreadPool::submitTask(std::shared_ptr<Task> sp)
{
	//获取锁,因为生产者和消费者要操作同一个队列
	std::unique_lock<std::mutex> lock(taskQueMtx_);

	//线程的通信  等待任务队列有空余   wait  
	//  wait_for(等这个条件到什么时候就撤了,返回值是false,表示等待超时,条件依然没有满足。提交任务失败!)
	//  wait_until
	//用户提交任务,最长不能阻塞超过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;
		//return task->getResult();//Task  Result   线程执行完task,task对象就被析构掉了
		return Result(sp, false);//无效的返回值,有效的任务返回值才取返回值
	}

	//如果有空余,把任务放入任务队列中
	taskQue_.emplace(sp);
	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(sp);//返回有效的返回值
	//return task->getResult(); //执行完task任务,tast对象就析构了!
}

//开启线程池
void ThreadPool::start(int initThreadSize)
{
	//设置线程池的运行状态,启动状态
	isPoolRunning_ = true;

	//记录初始线程个数
	initThreadSize_ = initThreadSize;
	curThreadSize_ = initThreadSize;

	//集中创建线程对象
	for (int i = 0; i < initThreadSize_; i++)
	{
		//创建thread线程对象的时候,把线程函数给到thread线程对象
		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_.emplace_back(std::move(ptr));
	}

	//启动所有线程  std::vector<Thread*> threads_;
	for (int i = 0; i < initThreadSize_; i++)
	{
		threads_[i]->start();//线程的启动需要去执行一个线程函数
		idleThreadSize_++;//记录初始空闲线程的数量
	}
}

//定义线程函数   线程池的所有线程从任务队列里面消费任务
void ThreadPool::threadFunc(int threadid)//线程函数返回,相应的线程也就结束了
{
	auto lastTime = std::chrono::high_resolution_clock().now();

	//所有任务必须执行完成,线程池才可以回收所有线程资源!!!
	for (;;)
	{
		std::shared_ptr<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);
				}

				//if (!isPoolRunning_)
				//{
				//	threads_.erase(threadid);//std::this_thread::getid()
				//	std::cout << "threadid:" << std::this_thread::get_id() << " exit!"
				//		<< std::endl;
				//	exitCond_.notify_all();
				//	return;//结束线程函数,就是结束当前线程了!
				//}
			}

			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->run();//执行任务;把任务的返回值setVal方法给到Result,基类指针调用派生类对象的同名覆盖方法
			task->exec();//用户还是使用run方法
		}
		
		idleThreadSize_++;
		lastTime = std::chrono::high_resolution_clock().now();//更新线程执行完任务的时间
	}
}

bool ThreadPool::checkRunningState() const
{
	return isPoolRunning_;
}

///    线程方法实现
int Thread::generateId_ = 0;

//线程构造
Thread::Thread(ThreadFunc func)
	: func_(func)
	, threadId_(generateId_++)
{}

//线程析构
Thread::~Thread() {}

//启动线程
void Thread::start()
{
	//创建一个线程来执行一个线程函数 pthread_create
	std::thread t(func_, threadId_);//C++11来说 线程对象t  和线程函数func_
	t.detach();//设置分离线程   pthread_detach  pthread_t设置成分离线程
}

int Thread::getId()const//获取线程的id
{
	return threadId_;
}


///    Task方法实现
Task::Task()
	: result_(nullptr)
{}

void Task::exec()//用户调用,还是执行基类的方法
{
	if (result_ != nullptr)//比较保险
	{
		result_->setVal(run());//这里发生多态调用
	}
}

void Task::setResult(Result* res)
{
	result_ = res;
}

///    Result方法的实现
Result::Result(std::shared_ptr<Task> task, bool isValid)
	: isValid_(isValid)
	, task_(task)//强智能指针接收
{
	task_->setResult(this);
}

Any Result::get()//用户调用的
{
	if (!isValid_)//判断有效还是无效
	{
		return "";
	}
	sem_.wait();//task任务如果没有执行完,这里会阻塞用户的线程!相当于-1操作
	return std::move(any_);//因为any_的成员变量是一个unique_ptr,没有左值的拷贝构造函数
}

void Result::setVal(Any any)//谁调用的呢???
{
	//存储task的返回值
	this->any_ = std::move(any);//因为any_的成员变量是一个unique_ptr,没有左值的拷贝构造函数
	sem_.post();//已经获取的任务的返回值,增加信号量资源,get方法就可以执行了,相当于+1操作
}

示例

// 线程池项目.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <chrono>
#include <thread>
using namespace std;

#include "threadpool.h"

/*
有些场景,是希望能够获取线程执行任务得返回值得
举例:
1 + 。。。 + 30000的和
thread1  1 + ... + 10000
thread2  10001 + ... + 20000
.....

main thread:给每一个线程分配计算的区间,并等待他们算完返回结果,合并最终的结果即可
*/

using uLong = unsigned long long;

class MyTask : public Task
{
public:
    MyTask(int begin, int end)
        : begin_(begin)
        , end_(end)
    {}
    //问题一:怎么设计run函数的返回值,可以表示任意的类型
    //Java Python   Object 是所有其它类类型的基类
    //C++17 Any类型
    Any run()//run方法最终就在线程池分配的线程中去做执行了!
    {
        std::cout << "tid:" << std::this_thread::get_id()
            << "begin!" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        uLong sum = 0;
        for (uLong i = begin_; i <= end_; i++)
            sum += i;
        std::cout << "tid:" << std::this_thread::get_id()
            << "end!" << std::endl;

        return sum;
    }

private:
    int begin_;
    int end_;
};

int main()
{
    {
        ThreadPool pool;
        pool.setMode(PoolMode::MODE_CACHED);
        //开始启动线程池
        pool.start(2);

        //linux上,这些Result对象也是局部对象,要析构的!!!
        Result res1 = pool.submitTask(std::make_shared<MyTask>(1, 100000000));
        Result res2 = pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));
        pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));
        pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));
        pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));

        //uLong sum1 = res1.get().cast_<uLong>();
        //cout << sum1 << endl; 
    } //这里Result对象也要析构!!! 在vs下,条件变量析构会释放相应资源的
    
    cout << "main over!" << endl;
    getchar();
#if 0
    //问题:ThreadPool对象析构以后,怎么样把线程池相关的线程资源全部回收?
    {
        ThreadPool pool;
        //用户自己设置线程池的工作模式
        pool.setMode(PoolMode::MODE_CACHED);
        //开始启动线程池
        pool.start(4);

        //如何设计这里的Result机制呢
        Result res1 = pool.submitTask(std::make_shared<MyTask>(1, 100000000));
        Result res2 = pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));
        Result res3 = pool.submitTask(std::make_shared<MyTask>(200000001, 300000000));
        pool.submitTask(std::make_shared<MyTask>(200000001, 300000000));

        pool.submitTask(std::make_shared<MyTask>(200000001, 300000000));
        pool.submitTask(std::make_shared<MyTask>(200000001, 300000000));

        //随着task被执行完,task对象没了,依赖于task对象的Result对象也没了
        uLong sum1 = res1.get().cast_<uLong>();//get返回了一个Any类型,怎么转成具体的类型呢?
        uLong sum2 = res2.get().cast_<uLong>();
        uLong sum3 = res3.get().cast_<uLong>();

        //Master - Slave线程模型
        //Master线程用来分解任务,然后给各个Slave线程分配任务
        //等待各个Slave线程执行完任务,返回结果
        //Master线程合并各个任务结果,输出
        cout << (sum1 + sum2 + sum3) << endl;
    }
    


    /*uLong sum = 0;
    for (uLong i = 1; i <= 300000000; i++)
        sum += i;
    cout << sum << endl;*/

    /*pool.submitTask(std::make_shared<MyTask>());
    pool.submitTask(std::make_shared<MyTask>());
    pool.submitTask(std::make_shared<MyTask>());
    pool.submitTask(std::make_shared<MyTask>());
    pool.submitTask(std::make_shared<MyTask>());
    pool.submitTask(std::make_shared<MyTask>());
    pool.submitTask(std::make_shared<MyTask>());
    pool.submitTask(std::make_shared<MyTask>());
    pool.submitTask(std::make_shared<MyTask>());*/
 
    getchar();

#endif
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值