C++11异步线程池

C++11异步线程池实现

教程来源:爱编程的大炳

构成:
1、管理者线程 -> 子线程,1个

  • 控制工作线程的数量:增加或减少
    2、若干个工作线程 -> 子线程,n个
    • 从任务队列中取任务,并处理
    • 任务队列为空,被阻塞(被条件变量阻塞)
    • 线程同步(互斥锁)
    • 当前数量 空闲的线程数量
    • 最小,最大线程数量
      3、任务队列 -> stl->queue
    • 互斥锁
    • 条件变量
      4、线程池开关 -> bool
class ThreadPool {
public:
private:
	thread* m_manager;	//管理者线程
	vector<thread> m_workers;	//工作线程
	atomic<int> m_minThread;	//最小线程数量
	atomic<int> m_maxThread;	//最大线程数量
	atomic<int> m_curThread;	//当前线程数量
	atomic<int> m_idleThread;	//空闲线程数量
	atomic<bool> m_stop;	//线程池开关
	queue<function<void(void)>> m_task;	//任务队列
	mutex m_queueMutex;	//任务队列的互斥锁
	condition_variable m_condition;	//用于阻塞消费者线程
};

这是先声明了一些成员变量,需要包含头文件include <atomic>include <thread>include <functional>

为什么要用atomic?

这是用于定义原子变量的一个类,保证这个变量是原子操作。

为什么要用function?

这里的function是一个对象包装器,它通常与bind一起使用,作用是让不同类型可调用对象实现类型统一,有了这个可调用对象之后,就可以拿到函数的地址,所以这个queue所保存的是一个函数指针类型

为什么用emplace_back

for (int i = 0; i < min; i++) {
    //这样写避免拷贝
    this->m_workers.emplace_back(thread(&ThreadPool::worker), this);
}

emplace_back()需要在添加的过程中去构造对象才能避免拷贝和移动,也就是需要在函数的括号中去构造要添加的对象

工作函数worker

void ThreadPool::worker(void)
{
	//调用load方法,是线程安全的 其实和!m_stop是一样的
	while (!m_stop.load()) {
		function<void(void)> task = nullptr;
		{
			unique_lock<mutex> locker(m_queueMutex);
			//这里要用while循环而不是if
			while (m_tasks.empty() && !m_stop) {
				m_condition.wait(locker);	//阻塞函数
				if (m_exitThread > 0) {
					m_curThread--;
					m_exitThread--;
					cout << "------ 线程退出了,ID: " << this_thread::get_id() << endl;
					unique_lock<mutex> lck(m_idsMutex);
					m_ids.emplace_back(this_thread::get_id());
					return;
				}
			}
			if (!m_tasks.empty()) {
				task = move(m_tasks.front());
				m_tasks.pop();
			}
		}
		if (task) {
			//执行前后不能忘了对空闲线程变量进行修改
			m_idleThread--;
			task();
			m_idleThread++;
		}
	}
}

理解这段代码非常重要,首先需要一个while循环来一直获取任务队列中的任务,里面的条件就是!m_stop,即只要线程池没有停止,就一直循环获取。然后就是对任务队列加锁,之后就是对任务队列进行判空操作,这个判空操作一定要写成while循环而不是if,这一点非常重要!因为里面涉及到的是wait阻塞函数,多线程编程时可能会遇到虚假唤醒的操作,也就是当前可能多个线程阻塞在这里了,然后在外面的某个地方调用了notify_all函数时会导致对空队列的操作导致报错,所以这里一定一定要用while循环!

构造函数

ThreadPool::ThreadPool(int min, int max) : m_maxThread(max), m_minThread(min),
m_stop(false), m_idleThread(min), m_curThread(min)
{
	// 创建管理者线程
	m_manager = new thread(&ThreadPool::manager, this);	//是类里面的成员函数就需要指定类
	//工作的线程
	for (int i = 0; i < min; i++) {
		//这样写避免拷贝
		//this->m_workers.emplace_back(thread(&ThreadPool::worker, this));
		thread t(&ThreadPool::worker, this);
		this->m_workers.insert(make_pair(t.get_id(), move(t)));	//一定要用move,thread类有不可复制性
	}
}

将原本的vector类m_workers改成了map之后,插入函数就有了变化,这里就变成了map类的插入函数insert,首先它需要一个键值对,分别是线程id和线程,所以需要先创建出一个线程的临时变量,然后就是move函数很重要,不能直接写t,而是要写成move(t),因为线程是不能被拷贝的

管理者函数

void ThreadPool::manager(void)
{
	while (!m_stop.load()) {
		//线程休眠3s
		this_thread::sleep_for(chrono::seconds(3));
		int idel = m_idleThread.load();
		int cur = m_curThread.load();
		//空闲线程过多 空闲线程在哪?---在wait函数阻塞着
		if (idel > cur / 2 && cur > m_minThread) {
			//每次销毁两个线程
			m_exitThread.store(2);
			m_condition.notify_all();
			unique_lock<mutex> lck(m_idsMutex);
			for (auto id : m_ids) {
				auto it = m_workers.find(id);
				if (it != m_workers.end()) {
					(*it).second.join();
					m_workers.erase(it);
					cout << "==========线程:" << (*it).first << "被销毁了 ..." << endl;
				}
			}
			m_ids.clear();
		}
		else if (idel == 0 && cur < m_maxThread) {
			//m_workers.emplace_back(thread(&ThreadPool::worker,this));
			thread t(&ThreadPool::worker, this);
			m_workers.insert(make_pair(t.get_id(), move(t)));
			m_curThread++;
			m_idleThread++;
		}
	}
}

管理者线程主要任务是控制线程数量,线程少的时候就创建新的工作线程,当线程多的时候就将空闲的线程回收,在这期间需要注意线程同步和各个原子变量的改变

同步线程池

ThreadPool.h

#pragma once
#include <thread>
#include <vector>
#include <atomic>
#include <queue>
#include <functional>
#include <mutex>
#include <map>
#include <condition_variable>

using namespace std;

/*
构成:
1、管理者线程 -> 子线程,1个
	- 控制工作线程的数量:增加或减少
2、若干个工作线程 -> 子线程,n个
	- 从任务队列中取任务,并处理
	- 任务队列为空,被阻塞(被条件变量阻塞)
	- 线程同步(互斥锁)
	- 当前数量 空闲的线程数量
	- 最小,最大线程数量
3、任务队列 -> stl->queue
	- 互斥锁
	- 条件变量
4、线程池开关 -> bool
*/

class ThreadPool {
public:
	//hardware_concurrency()用于获取计算机核数
	ThreadPool(int min = 2, int max = thread::hardware_concurrency());
	~ThreadPool();

	//添加任务 -> 任务队列 函数参数是可调用对象类型
	void addTask(function<void(void)> task);

private:
	void manager(void);
	void worker(void);
private:
	thread* m_manager;
	map<thread::id, thread> m_workers;
	vector<thread::id> m_ids;	//存储已经退出任务函数的线程ID
	atomic<int> m_minThread;
	atomic<int> m_maxThread;
	atomic<int> m_curThread;
	atomic<int> m_idleThread;
	atomic<int> m_exitThread;
	atomic<bool> m_stop;
	queue<function<void(void)>> m_tasks;
	mutex m_queueMutex;
	mutex m_idsMutex;
	condition_variable m_condition;
};

ThreadPool.cpp

#include "ThreadPool.h"
#include <iostream>

ThreadPool::ThreadPool(int min, int max) : m_maxThread(max), m_minThread(min),
m_stop(false), m_idleThread(min), m_curThread(min)
{
	cout << "max = " << max << endl;
	// 创建管理者线程
	m_manager = new thread(&ThreadPool::manager, this);	//是类里面的成员函数就需要指定类
	//工作的线程
	for (int i = 0; i < min; i++) {
		//这样写避免拷贝
		//this->m_workers.emplace_back(thread(&ThreadPool::worker, this));
		thread t(&ThreadPool::worker, this);
		this->m_workers.insert(make_pair(t.get_id(), move(t)));	//一定要用move,thread类有不可复制性
	}
}

ThreadPool::~ThreadPool()
{
	m_stop = true;
	m_condition.notify_all();
	//不加引用的话就会发生线程的拷贝
	for (auto& it : m_workers) {
		thread& t = it.second;
		if (t.joinable()) {
			cout << "********** 线程 " << t.get_id() << "将要退出了..." << endl;
			t.join();
		}
	}
	if (m_manager->joinable()) {
		m_manager->join();
	}
	delete m_manager;
}

void ThreadPool::addTask(function<void(void)> task)
{
	//任务队列是一个共享资源 在使用的时候需要先加锁
	//调用自带的锁类,就像智能指针一样,遵循RAII原则
	{
		lock_guard<mutex> locker(m_queueMutex);	//用unique_lock也是一样的效果
		m_tasks.emplace(task);	//和push_back的效率其实是一样的
	}
	m_condition.notify_one();	//唤醒一个等待的线程
}

void ThreadPool::manager(void)
{
	while (!m_stop.load()) {
		//线程休眠3s
		this_thread::sleep_for(chrono::seconds(1));
		int idel = m_idleThread.load();
		int cur = m_curThread.load();
		//空闲线程过多 空闲线程在哪?---在wait函数阻塞着
		if (idel > cur / 2 && cur > m_minThread) {
			//每次销毁两个线程
			m_exitThread.store(2);
			m_condition.notify_all();
			//注意到在manager和worker都有用到m_ids,所以这里需要加锁
			unique_lock<mutex> lck(m_idsMutex);
			for (auto id : m_ids) {
				auto it = m_workers.find(id);
				if (it != m_workers.end()) {
					cout << "==========线程:" << (*it).first << "被销毁了 ..." << endl;
					(*it).second.join();
					m_workers.erase(it);
				}
			}
			m_ids.clear();
		}
		else if (idel == 0 && cur < m_maxThread) {
			//m_workers.emplace_back(thread(&ThreadPool::worker,this));
			thread t(&ThreadPool::worker, this);
			m_workers.insert(make_pair(t.get_id(), move(t)));
			m_curThread++;
			m_idleThread++;
		}
	}
}

void ThreadPool::worker(void)
{
	//调用load方法,是线程安全的 其实和!m_stop是一样的
	while (!m_stop.load()) {
		function<void(void)> task = nullptr;
		{
			unique_lock<mutex> locker(m_queueMutex);
			//这里要用while循环而不是if
			while (m_tasks.empty() && !m_stop) {
				m_condition.wait(locker);	//阻塞函数
				if (m_exitThread > 0) {
					m_curThread--;
					m_exitThread--;
					m_idleThread--;
					cout << "------ 线程退出了,ID: " << this_thread::get_id() << endl;
					unique_lock<mutex> lck(m_idsMutex);
					m_ids.emplace_back(this_thread::get_id());
					return;
				}
			}
			if (!m_tasks.empty()) {
				cout << "取出了一个任务..." << endl;
				task = move(m_tasks.front());
				m_tasks.pop();
			}
		}
		if (task) {
			//执行前后不能忘了对空闲线程变量进行修改
			m_idleThread--;
			task();
			m_idleThread++;
		}
	}
}

void calc(int x, int y) {
	int z = x + y;
	cout << "z = " << z << endl;
	this_thread::sleep_for(chrono::seconds(2));
}

int main() {
	ThreadPool pool;
	for (int i = 0; i < 10; i++) {
        //由于calc是带参数的函数,所以就需要bind转化为可调用对象
		auto obj = bind(calc, i, i * 2);
		pool.addTask(obj);
	}
	getchar();	//阻塞主线程

	return 0;
}

同步与异步的区别

  • 同步:在同步模式下,操作是按顺序依次执行的。当一个操作开始执行时,程序会暂停并等待该操作完成,得到结果后才会继续执行后续操作。可以将其理解为 “串行” 执行,操作之间具有严格的先后顺序。例如在银行办理业务,需要在一个业务窗口依次完成各项业务,一项业务处理完才能进行下一项。
  • 异步:异步操作允许程序在发起一个操作后,不必等待该操作完成,就可以继续执行后续的代码。当异步操作完成时,会通过某种机制(如回调函数、事件通知、Promise 对象等)通知程序进行后续处理。类似于在餐厅点餐,顾客下单后可以继续做其他事情(如看报纸、玩手机),等餐做好后服务员会通知顾客取餐。

异步线程池

ThreadPool.h

#pragma once
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <map>
#include <future>
#include <memory>
/*
构成:
	1、管理者线程->子线程,1个
		- 控制工作线程的数量:增加或减少
	2、若干工作线程->子线程 n个
		- 从任务队列中取任务,并处理
		- 任务队列为空,被阻塞(被条件变量阻塞)
		- 线程同步(互斥锁)
		- 当前数量,空闲的线程数量
		- 最大、最小线程数量
	3、任务队列-> stl->queue
		- 互斥锁
		- 条件变量
	4、线程池开关 -> bool
*/

class ThreadPool {
public:
	ThreadPool(int min = 2, int max = std::thread::hardware_concurrency());	//hardware_concurrency()用来求电脑的核心数
	~ThreadPool();

	//添加任务->任务队列
	void addTask(std::function<void(void)> task);

	//只能在这里实现
	template<typename F, typename... Args>
	auto addTask(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
		//1、package_task
		using returntype = typename std::result_of<F(Args...)>::type;
		auto mytask = std::make_shared<std::packaged_task<returntype()>> (
			std::bind(std::forward<F>(f), std::forward<Args>(args)...)
			);
		//2、得到future
		std::future<returntype> res = mytask->get_future();
		//3、任务函数添加到任务队列
		m_queueMutex.lock();
		m_tasks.emplace([mytask]() {
			(*mytask)();
			});
		m_queueMutex.unlock();
		m_condition.notify_one();

		return res;
	}

private:
	void manager(void);
	void worker(void);

private:
	std::thread* m_manager;
	std::map<std::thread::id, std::thread> m_workers;
	std::vector<std::thread::id> m_ids;	//存储已经退出了任务函数的线程ID
	//原子变量 是线程安全的 不确定是否会是线程中需要的共享资源就都设置为原子变量
	std::atomic<int> m_minThread;
	std::atomic<int> m_maxThread;
	std::atomic<int> m_curThread;
	std::atomic<int> m_idleThread;
	std::atomic<bool> m_stop;
	std::queue<std::function<void(void)>> m_tasks;
	std::atomic<int> m_exitThread;

	std::mutex m_queueMutex;
	std::mutex m_idsMutex;
	std::condition_variable m_condition;

};

ThreadPool.cpp

#pragma once
#include "ThreadPool.h"

ThreadPool::ThreadPool(int min, int max) : m_maxThread(max), m_minThread(min), m_stop(false), m_curThread(min), m_idleThread(min) {
	//std::cout << "max = " << max << std::endl;
	//创建管理者线程
	m_manager = new std::thread(&ThreadPool::manager, this);	//如果是类里面的成员函数,第二个参数必须要是this
	//工作的线程
	for (int i = 0; i < min; i++) {
		//向线程数组中添加工作线程 这个线程在弹出数组后销毁 不能定义一个临时变量再emplace_back emplace_back比push_back效率高 没有拷贝构造的过程
		std::thread t(&ThreadPool::worker, this);
		m_workers.insert(make_pair(t.get_id(), std::move(t)));	//用move实现资源转移	线程对象不允许拷贝 单例模式
	}
}

ThreadPool::~ThreadPool() {
	m_stop = true;
	m_condition.notify_all();	//唤醒所有阻塞的线程
	for (auto& it : m_workers) {
		//引用类型的迭代器不需要再解引用
		std::thread& t = it.second;
		if (t.joinable()) {
			std::cout << "********* 线程 " << t.get_id() << " 将要退出了..." << std::endl;
			t.join();
		}
	}
	if (m_manager->joinable()) {
		m_manager->join();
	}
	delete m_manager;
}

void ThreadPool::addTask(std::function<void(void)> task) {
	//添加作用域
	{
		std::unique_lock<std::mutex> locker(m_queueMutex);
		m_tasks.emplace(task);
	}
	m_condition.notify_one();
}

void ThreadPool::manager(void) {
	while (!m_stop.load()) {
		std::this_thread::sleep_for(std::chrono::seconds(1));
		int idle = m_idleThread.load();
		int cur = m_curThread.load();
		if (idle > cur / 2 && cur > m_minThread) {
			//每次销毁两个线程
			m_exitThread.store(2);	//store方法相当于赋值为2
			m_condition.notify_all();
			std::unique_lock<std::mutex> lck(m_idsMutex);
			for (auto& id : m_ids) {
				auto it = m_workers.find(id);
				if (it != m_workers.end()) {
					std::cout << "========线程:" << (*it).first << "被销毁了..." << std::endl;
					(*it).second.join();
					m_workers.erase(it);
				}
			}
			m_ids.clear();
		}
		else if (idle == 0 && cur < m_maxThread) {
			std::thread t(&ThreadPool::worker, this);
			m_workers.insert(make_pair(t.get_id(), std::move(t)));
			m_curThread++;
			m_idleThread++;
		}
	}
}

void ThreadPool::worker(void) {
	//.load加载原子对象数据,不用.load也行
	while (!m_stop.load()) {
		std::function<void(void)> task = nullptr;
		{
			std::unique_lock<std::mutex> locker(m_queueMutex);
			while (m_tasks.empty() && !m_stop) {
				//阻塞之前会把锁解开
				m_condition.wait(locker);
				if (m_exitThread.load() > 0) {
					m_curThread--;
					m_idleThread--;
					m_exitThread--;
					std::cout << "--------线程退出了,ID:" << std::this_thread::get_id() << std::endl;
					std::unique_lock<std::mutex> lck(m_idsMutex);
					m_ids.emplace_back(std::this_thread::get_id());
					return;
				}
			}
			if (!m_tasks.empty()) {
				std::cout << "取出了一个任务..." << std::endl;
				task = std::move(m_tasks.front());	//使用move防止拷贝
				m_tasks.pop();
			}
		}
		if (task) {
			m_idleThread--;	//空闲线程-1
			task();
			m_idleThread++;	//空闲线程+1
		}
	}
}

void calc(int x, int y) {
	int z = x + y;
	std::cout << "z = " << z << std::endl;
	std::this_thread::sleep_for(std::chrono::seconds(2));	//休眠两秒
}

int calc1(int x, int y) {
	int z = x + y;
	std::this_thread::sleep_for(std::chrono::seconds(2));	//休眠两秒
	return z;
}

int main() {

	ThreadPool pool;
	std::vector<std::future<int>> res;
	//for (int i = 0; i < 10; i++) {
	//	auto obj = std::bind(calc, i, i * 2);
	//	pool.addTask(obj);
	//}
	//getchar();

	for (int i = 0; i < 10; i++) {
		res.emplace_back(pool.addTask(calc1, i, i * 2));
	}
	
	for (auto& item : res) {
		std::cout << "线程执行的结果:" << item.get() << std::endl;
	}

	return 0;
}

异步线程池主要是加了异步类、泛型编程、future类、智能指针的写法,难点也就在泛型编程的那一个函数,学第二遍了还不是很懂…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值