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类、智能指针的写法,难点也就在泛型编程的那一个函数,学第二遍了还不是很懂…