超详细!手撕C++线程池!(大家圣诞节快乐呀!!)

线程池流程图:

(一)首先确定好咱们的线程池类,并写好基本的接口

class ThreadPool
{
public:
	/// <summary>
	/// 初始化线程池
	/// </summary>
	/// <param name="num">线程数量</param>
	void Init(int num);

	/// <summary>
	/// 启动所有线程
	/// </summary>
	void Start();

	/// <summary>
	/// 线程池退出
	/// </summary>
	void Stop();

	bool is_exit() { return is_exit_; }

private:
	//线程池线程入口函数
	void Run();

	int thread_num_ = 0;//线程数量
	std::mutex mux_;

	bool is_exit_ = false; //线程池退出

};

1、初始化线程池数量(线程安全)

void ThreadPool::Init(int num)
{
	unique_lock<mutex> lock(mux_);
	this->thread_num_ = num;
}

2、启动线程池里的线程(线程安全)

void ThreadPool::Start()
{
	unique_lock<mutex> lock(mux_);

	if (thread_num_ <= 0)
	{
		cerr << "Please Init ThreadPool" << endl; 
			return;
	}
	if (!threads_.empty())//判断线程池是否是空
	{
		cerr << " Thread pool has start!" << endl;
		return;
	}

	cout << "Pool Start " << endl;

	for (int i = 0; i < thread_num_; i++)
	{
		auto th = make_shared<thread>(&ThreadPool::Run, this);
		threads_.push_back(th);//复制,shared_ptr引用计数加一
	}//出栈区引用计数减一

}

//线程入口函数
void ThreadPool::Run()
{
	cout << "begin thread pool " << this_thread::get_id() << endl;


	cout <<  "end thread pool "  << this_thread::get_id() << endl;
}

这里通过智能指针shared_ptr来管理线程,通过智能指针来自动管理线程的生命周期

3、线程池退出

void ThreadPool::Stop()
{
	is_exit_ = true;

    threads_.clear();//清理线程池

}

(二)确定任务接口

class Task
{
public:
	virtual int Run() = 0;

};
class Mytask : public Task
{
public:

	int Run()
	{
		cout << " =============================== " << endl;
		cout << this_thread::get_id() << "MyTask"  << name << endl;
		for (int i = 0; i < 10; i++)
		{
			if (is_exit())break;
			cout << " . "<< flush ;
			this_thread::sleep_for(1s);
		}
		return 0;
	}

};

1、给线程池加入用来加入任务的接口AddTask()和用于捕获任务的接口 GetTask()

class ThreadPool
{
public:
	/// <summary>
	/// 初始化线程池
	/// </summary>
	/// <param name="num">线程数量</param>
	void Init(int num);

	/// <summary>
	/// 启动所有线程,必须先调用Init
	/// </summary>
	void Start();

	/// <summary>
	/// 线程池退出
	/// </summary>
	void Stop();
    
    //线程池退出标志位
	bool is_exit() { return is_exit_; }
    
    //通过shared_ptr来管理任务链表,出栈自动释放
	void AddTask( std::shared_ptr<Task> task );

	std::shared_ptr<Task> GetTask();

private:
	//线程池线程入口函数
	void Run();
	int thread_num_ = 0;//线程数量
	std::mutex mux_;
    
    //线程池容器
	std::vector< std::shared_ptr<std::thread> > threads_;
    
    //任务链表
	std::list< std::shared_ptr<Task> > tasks_;

	std::condition_variable cv_;//条件变量

	bool is_exit_ = false; //线程池退出

};

2、向线程池里加入任务(线程安全)

void ThreadPool::AddTask(std::shared_ptr<Task> task)
{
	unique_lock<mutex> lock(mux_);
	tasks_.push_back(task);//把任务加入任务链表
	lock.unlock();
	cv_.notify_one();
}

3、线程池捕获任务(线程安全)

std::shared_ptr<Task> ThreadPool::GetTask()
{
	unique_lock<mutex> lock(mux_);//线程安全
    //任务链表为空,阻塞等待通知
	if (tasks_.empty())
		cv_.wait(lock); 
    //收到通知以后链表还是空说明是通知退出线程池
	if (tasks_.empty())
		return nullptr;
    //将任务取出返回出去
	auto task = tasks_.front();
    //任务弹出队列
	tasks_.pop_front();

	return task;
}

4、重写线程入口函数

void ThreadPool::Run()
{
	cout << "begin thread pool " << this_thread::get_id() << endl;

	while (!is_exit())
	{
		auto task = GetTask();

		if (!task)continue;

		try {
			task->Run();
		}
		catch (...)//表示捕获任何类型的异常
		{

		}
	}

	cout <<  "end thread pool "  << this_thread::get_id() << endl;
}

5、任务处理流程思路分析

当启动线程进入线程入口函数以后会再调用任务捕获函数,由于没有收到信号通知,线程会阻塞等待条件变量通知 cv_.wait(lock);

当我们在主函数里向线程池添加任务时  pool.AddTask(task3); ,线程会收到条件变量通知,即通过cv_.notify_one来按顺序一个个通知在排队的线程,

(注意!在cv_.notify_one之前要先解锁unlock,因为wait收到信号以后会再次锁住资源,如果这里没释放锁,则wait那边就会一直等待锁资源,照成死锁!!)

此时任务捕获函数中就会接收到信号,然后结束阻塞

(三)取得任务返回值,线程回到线程池

1、重写任务对象和线程入口函数

class Task
{
public:
	virtual int Run() = 0;
	std::function<bool()> is_exit = nullptr;
	auto GetReturn()
	{
		//阻塞等待set_value
		return proms_.get_future().get();
	}
	void SetValue(int v)
	{
		proms_.set_value(v);
	}
private:
	//用来接收返回值
	std::promise<int> proms_;
};

线程入口函数 :

	while (!is_exit())
	{
		auto task = GetTask();

		if (!task)continue;
		try {
			auto re = task->Run();
			task->SetValue(re);
		}
		catch (...)//表示捕获任何类型的异常
		{

		}
	}

2、思路分析

如果想在线程池外拿到任务处理的结果就需要通过promise—future来异步获取结果

当在外面调用GetReturn()时,外面的主线程就会阻塞在这里promise.get_future().get();,等待promise.set_value()设置值,当SetValue()在线程入口函数Run里被调用时,外面主线程就会收到任务返回值,取消阻塞

(四)退出线程池

1、重写Stop函数

void ThreadPool::Stop()
{
	is_exit_ = true;
	cv_.notify_all();

	for (auto& th : threads_)
	{
		th->join();
	}
	unique_lock<mutex> lock(mux_);

	threads_.clear();
}

设置is_exit_为true以后GetTask()会返回nullptr,防止还有线程阻塞在前面的wait,通过条件变量

cv_.notify_all()给所有线程发通知,取消阻塞,然后退出给task返回nullptr

线程池搭建完成!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值