C++—线程池
1、池
什么叫池?
它是预先从系统中申请一大堆的资源,然后使用完成后不还给操作系统,然后保持复用。
有什么好处?
从应用层向操作系统去申请资源(内存,线程)需要从应用层切入内核,分配完成后再切换回应用层使用。
这一大堆资源是内存就是内存池,是线程就是线程池。
什么时候用池?
频繁使用、释放,且资源占用不大的地方改成池操作。
使用时间很短,但是使用的频率很高。
2、预备知识
2.1 为什么要使用智能指针 (防止内存泄露)
上述代码中的x,y在程序运行结束后会自动释放,但是p是使用new在堆上面分配的,如果不使用delete释放内存,那么这块指向这段内存的指针p会随着函数的结束而消失,这块内存无人问津,即内存泄露。
2.2 智能指针类型
在现代 C++ 编程中,智能指针是管理动态内存和资源的关键工具。<memory> 头文件提供了几种智能指针,包括 std::unique_ptr、std::shared_ptr 和 std::weak_ptr。每种智能指针有其独特的用途和特点。
2.2.1 std::unique_ptr
std::unique_ptr 是一种独享的智能指针(一个指针只能指向一段内存),它负责管理一个动态分配的对象的生命周期,并保证在 std::unique_ptr 被销毁时,所管理的对象会被正确释放。std::unique_ptr 不能被复制,但可以被移动。
这段代码前5行代码是按照函数运行顺序进行的,但是在func函数运行结束后,自动执行了Ball类的析构函数打印了A ball disappears。避免了内存的泄露。
2.2.2 std::shared_ptr
std::shared_ptr 是一种共享的智能指针,它允许多个 std::shared_ptr 实例共享对同一个动态分配对象的所有权。对象的生命周期由引用计数控制,当最后一个 std::shared_ptr 被销毁时,对象才会被释放。
建议使用make_shared创建智能指针。
那是如何管理内存的?
resent会重置智能指针,不再指向原来物体,计数归0,自动释放内存。
2.2.3 std::weak_ptr(弱指针)
std::weak_ptr 是一种弱引用智能指针,它不影响对象的引用计数。std::weak_ptr 主要用于解决 std::shared_ptr 的循环引用问题。通过 std::weak_ptr 可以安全地观察对象(检查数据是否存在),但不控制对象的生命周期。
只能告诉你资源是否存在,就算访问数据也要借助std::shared_ptr(lock方法)进行访问。
2.3 原子操作、锁和CAS
<atomic> 头文件提供了原子操作功能,在并发编程中尤为重要。原子操作是不可分割的操作,这意味着在执行期间不会被中断。在多线程环境中,原子操作可以用来安全地操作共享数据,而无需使用传统的锁机制(如互斥锁),从而提高性能和减少复杂性。
这两条指令只有一条会成功。 另外一条会被CPU拒绝。
使用CAS(比较和更新,CPU将其称为原子操作)
CAS代表了一个线程失败,另外一个线程就会成功。
假设我们将以下两个线程整合为原子操作。
如何解决这个问题呢? 给线程上锁上锁(lock)
lock与cas loop(cas循环)的区别?
主要组件
std::atomic:原子类型模板,用于定义可以在多线程环境下安全访问的变量。
std::atomic_flag:最基本的原子类型,用于实现锁的功能。
原子操作函数:
如 load(), store(), exchange(), compare_exchange_weak(),compare_exchange_strong() 等,用于执行原子操作。
2.3.1. std::atomic
std::atomic 是一个模板类,用于创建原子类型的变量。它提供了一系列原子操作函数,可以保证操作的原子性和线程安全性。以下是一些常用的操作函数:
load():以原子方式读取值。
store(value):以原子方式设置值。
exchange(value):以原子方式设置值,并返回旧值。
compare_exchange_weak(expected, desired):以原子方式比较当前值与期望值,如果相等则设置为新值,否则更新期望值为当前值。
compare_exchange_strong(expected, desired):与 compare_exchange_weak 类似,但提供更强的保证,适用于复杂的比较和交换操作。
2.3.2 std::atomic_flag
std::atomic_flag 是一种简单的原子类型,通常用于实现低级别的锁机制。它只有两个操作:test_and_set() 和 clear()。test_and_set() 会设置标志位并返回旧值,而 clear() 会清除标志位。
2.3.3. 内存序 (Memory Order)
原子操作还涉及内存序,确定操作的可见性和顺序。主要有以下几种内存序:
std::memory_order_relaxed:最宽松的内存序,不保证任何顺序。
std::memory_order_consume:保证操作依赖关系的顺序,但对性能影响较大。
std::memory_order_acquire:保证操作之前的所有操作都在当前操作之前完成。
std::memory_order_release:保证当前操作之前的所有操作都在当前操作之后完成。
std::memory_order_acq_rel:结合 acquire 和 release。
std::memory_order_seq_cst:最严格的内存序,保证所有操作的全局顺序。
总结
std::atomic 提供了原子操作,适用于需要在多线程环境中进行无锁操作的场景。
std::atomic_flag 用于实现低级别的锁机制,如自旋锁。
了解内存序是使用原子操作时的一个重要方面,它帮助你控制操作的顺序和可见性,从而确保程序的正确性和效率。
2.4 并发与并行
一般情况下,两种情况统称为并发程序。面试如果问到区别,可以这样描述。
并发程序相当于单核CPU中执行任务时,多个任务本身是串行的,但是由于每个任务执行时间都很短,可以看做是同时执行的,这种称为并发。而并行程序是真正的多个任务同时进行,在多核CPU中,可以同时进行多个任务。
2.5 多线程的优势
具体描述:多线程程序设计不一定是好的,得分情况。在IO密集型中,使用多线程是好的,因为IO操作是可以把程序阻塞住的,这就导致CPU空闲了,浪费资源。而cpu密集型
2.6 多线程的消耗?
2.7 线程池
2.7.1 线程的优势
2.7.2 fixed模式线程池
2.7.3 cached模式线程池
2.8 线程同步
2.8.1 线程互斥
概念:是一种同步机制,用来防止多个线程同时访问共享资源,从而避免竞争条件
互斥锁mutex
atomic原子操作
//使用锁是为了保证原子操作,避免竞态条件。
总结:竞态条件:代码片段在多线程环境下执行,随着线程的调动顺序不同,而得到不同的使用结果。这种情况是不行的。存在竞态条件这段代码称为临界区代码段。
线程互斥是为了避免竞争条件的。分类互斥锁和原子操作。
如果在多线程环境下是不存在竞态条件的===》可重入的
存在竞态条件的===》不可重入的!则需要使用原子操作,线程互斥。
使用互斥锁(lock或者trylock)。互斥锁使得一个线程接收了任务,其他线程不在接收。互斥锁保证了只有一个线程能够进入临界区代码段。
(lock和trylock(活锁)的区别:lock:用于阻塞获取锁,适合需要严格确保独占访问的场景。
trylock:用于非阻塞尝试获取锁,允许线程有其他选择的场景。)
CAS操作(无锁机制)无锁队列、无锁链表、无锁数组。
一般情况下,在C++11环境下,都不使用这种lock和unlock的情景,因为你写了unlock和delete一样,写了也不一定能运行到,会造成内存泄露。因此使用智能锁。lock_guard(和智能指针一样,在构造的时候加锁,在析构的时候解锁)和unique_lock(提供了手动上锁和解锁的操作,可以和条件变量一起操作。)
2.8.2 线程通信(生产者消费者模型)
条件变量 condition_variable
信号量 semaphone(C++20信号量)
主要包括条件变量应用和信号量应用。
线程A代码段的执行要依赖于线程B代码段的执行结束,因此就涉及到线程的通信了。 就算线程A先进入执行,也需要让线程A进入等待,等B运行结束再执行。(条件变量和信号量都可以执行)
1、条件变量 condition_variable一般结合unique_lock一起使用,
在生产者消费者模型中,如生产者一直占着锁,但是容器此时以及已经满了,那麽就需要让生产者线程等待。wait总共做了两件事情,1、改变了线程的状态(等待)2、把互斥锁释放了.
如果容器不满,抢占锁。
总结:
1、共享内存:线程可以通过共享内存区域交换数据,例如全局变量或静态变量。然而,使用共享内存需要适当的同步机制来避免竞争条件。
2、同步机制:互斥锁(Mutex):用于保护共享资源,确保同一时间只有一个线程能够访问资源。条件变量(Condition Variable):允许线程在特定条件下进行等待和通知,用于协调线程之间的执行顺序。
3、线程间通信的模式:生产者-消费者模式:一个线程生产数据并将其放入缓冲区,另一个线程从缓冲区取出数据进行处理。
发布-订阅模式:一个线程发布消息,多个订阅线程接收并处理这些消息。
2、信号量(semaphore)
mutex的资源计数是0或者1.而semphone的资源计数可以是任意的。
总结:信号量semaphore和互斥锁的区别?
互斥锁是做线程互斥的,信号量是做线程通信的。其次,mutex只能是哪个线程获取锁,由哪个线程释放锁!sem不同,sem.wait()和sem.post()可以处在不同的线程中调用。
PV操作:P代表sem.wait()等待,V代表sem.post()是信号量资源加1.而对于条件变量使用cond.waut()代表P操作,cond.notify代表V操作。
3、整体架构梳理
使用方法:
框架图:
4、源代码
(博主正在敲写中,敲完会上传到github,敬请期待)
4.1 ThreadPool类实现
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <vector>
#include<queue>
#include<memory>
#include<atomic>
#include<mutex>
#include<condition_variable>
//任务抽象基类
class Task
{
public:
//用户可以自定义任意任务类型,从Task继承,重写run方法,实现自定义任务处理
virtual void run() = 0;
};
//线程池支持的模式
enum class poolMode //enum class是为了解决枚举不同,但是枚举项相同的情况
{
MODE_FIXED, //固定数量的线程
MODE_CACHED, //线程数量可动态可增长
};
//线程类型
class Thread
{
};
//线程池类型
class ThreadPool
{
public:
ThreadPool();
~ThreadPool();
//设置线程池的工作模式
void setMode(poolMode mode);
//设置task任务队列的上限阈值
void setTaskQueMaxThreshHold(int threshhold);
//给线程池提交任务
void submitTask(std::shared_ptr<Task>sp); //因为任务周期不知道,因此传一个智能指针
//开启线程池
void start();
private:
std::vector<Thread*>threads_;//threads_ 线程列表
size_t initThreadSize_; //初始的线程数量
std::queue<std::shared_ptr<Task>>taskQue_; //任务队列
std::atomic_int taskSize_; //任务数量
int taskQueMaxThreshHold_; //任务队列数量上限阈值
std::mutex taskQueMtx_; //保证任务队列的线程安全 互斥锁
std::condition_variable notFull_;
std::condition_variable notEmpty_; //表示任务队列不空
poolMode poolMode_; //当前线程池的工作模式
};
#endif
4.2 ThreadPool方法接口实现以及线程函数的实现
threadpool.cpp文件
#include "threadpool.h"
#include<functional>
#include<thread>
#include<iostream>
const int TASK_MAX_THRESHHOLD = 1024;
//线程池构造
ThreadPool::ThreadPool()
:initThreadSize_(4)
, taskSize_(0)
, taskQueMaxThreshHold_(TASK_MAX_THRESHHOLD)
, poolMode_(PoolMode::MODE_FIXED)
{}
//线程池析构
ThreadPool::~ThreadPool()
{}
//设置线程池的工作模式
void ThreadPool::setMode(PoolMode mode)
{
poolMode_ = mode;
}
//设置task任务队列的上限阈值
void ThreadPool::setTaskQueMaxThreshHold(int threshhold)
{
taskQueMaxThreshHold_ = threshhold;
}
//给线程池提交任务 用户调用接口 传入任务 生产任务
void ThreadPool::submitTask(std::shared_ptr<Task>sp) //因为任务周期不知道,因此传一个智能指针
{
}
//开启线程池
void ThreadPool::start(int initThreadSize)
{
//记录初始线程个数
initThreadSize_ = initThreadSize;
//创建线程对象
for (int i = 0; i < initThreadSize_; i++)
{
//创建thread线程对象的时候,把threadpool的线程函数给到thread对象 //绑定
threads_.emplace_back(new Thread(std::bind(&ThreadPool::threadFunc,this)));
}
//启动所有线程
for (int i = 0; i < initThreadSize_; i++)
{
threads_[i]->start();
}
}
//定义线程函数 所有线程函数是从任务队列中消费任务
void ThreadPool::threadFunc()
{
std::cout << "begin treadfunc tid:" << std::this_thread::get_id()
<< std::endl;
std::cout << "end treadfunc tid:" << std::this_thread::get_id()
<<std::endl;
}
线程方法实现
//线程构造
Thread::Thread(ThreadFunc func)
:func_(func)
{
}
//线程析构
Thread::~Thread()
{
}
//启动线程
void Thread::start()
{
//执行一个线程函数
std::thread t(func_); //C++11的线程类 线程对象t 和线程函数func_
t.detach(); //设置分离线程 将线程对象和线程函数进行分离,即在启动线程这个函数结束后,线程对象消失就消失了,不影响函数的运行
}
threadpool.h文件
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <vector>
#include<queue>
#include<memory>
#include<atomic>
#include<mutex>
#include<condition_variable>
#include<functional>
//任务抽象基类
class Task
{
public:
//用户可以自定义任意任务类型,从Task继承,重写run方法,实现自定义任务处理
virtual void run() = 0;
};
//线程池支持的模式
enum class PoolMode //enum class是为了解决枚举不同,但是枚举项相同的情况
{
MODE_FIXED, //固定数量的线程
MODE_CACHED, //线程数量可动态可增长
};
//线程类型
class Thread
{
public:
//线程函数对象类型
using ThreadFunc = std::function<void()>;
//线程构造
Thread(ThreadFunc func);
//线程析构
~Thread();
//启动线程
void start();
private:
ThreadFunc func_;
};
//线程池类型
class ThreadPool
{
public:
//线程池构造
ThreadPool();
//线程池析构
~ThreadPool();
//设置线程池的工作模式
void setMode(PoolMode mode);
//设置task任务队列的上限阈值
void setTaskQueMaxThreshHold(int threshhold);
//给线程池提交任务
void submitTask(std::shared_ptr<Task>sp); //因为任务周期不知道,因此传一个智能指针
//开启线程池
void start(int initThreadSize=4);
//禁止拷贝构造和拷贝赋值操作,用户使用线程池可以单独创建一个线程池对象,对线程池本身不能操作
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
private:
//定义线程函数
void threadFunc();
private:
std::vector<Thread*>threads_;//threads_ 线程列表
size_t initThreadSize_; //初始的线程数量
std::queue<std::shared_ptr<Task>>taskQue_; //任务队列
std::atomic_int taskSize_; //任务数量
int taskQueMaxThreshHold_; //任务队列数量上限阈值
std::mutex taskQueMtx_; //保证任务队列的线程安全 互斥锁
std::condition_variable notFull_;
std::condition_variable notEmpty_; //表示任务队列不空
PoolMode poolMode_; //当前线程池的工作模式
};
#endif
测试文件:
#include<iostream>
#include<chrono>
#include<thread>
#include"threadpool.h"
int main()
{
ThreadPool pool;
pool.start(6);
//主线程结束后,分离线程给自动回收了,分离线程可能还没启动
std::this_thread::sleep_for(std::chrono::seconds(5)); //睡眠函数,主线程睡眠5s
}
测试结果:
碰到的主要问题:
//启动线程
void Thread::start()
{
//执行一个线程函数
std::thread t(func_); //C++11的线程类 线程对象t 和线程函数func_
t.detach(); //设置分离线程 将线程对象和线程函数进行分离,即在启动线程这个函数结束后,线程对象消失就消失了,不影响函数的运行
}
启动函数函数中设定了分离线程,但是测试阶端分离线程还没启动主线程已经结束了,测试没有输出?
解决方案:使用了睡眠函数,使得主线程睡眠一段时间,等待分离线程完成。
4.3 submitTask给线程池提交任务代码输出
//给线程池提交任务 用户调用接口 传入任务 生产任务
void ThreadPool::submitTask(std::shared_ptr<Task>sp) //因为任务周期不知道,因此传一个智能指针
{
//获取锁
std::unique_lock<std::mutex>lock(taskQueMtx_);
//线程的通信 等待任务队列有空余
/*while (taskQue_.size() == taskQueMaxThreshHold_) //与lamda表达式意义相同
{
notFull_.wait(lock);
}*/
notFull_.wait(lock, [&]()->bool {return taskQue_.size() < taskQueMaxThreshHold_; }); //lamda表达式
//如果有空余,把任务放入任务队列中
taskQue_.emplace(sp);
taskSize_++;
//因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务
notEmpty_.notify_all();
}
4.4 代码功能测试
threadpool.cpp文件
#include "threadpool.h"
#include<functional>
#include<thread>
#include<iostream>
const int TASK_MAX_THRESHHOLD = 1024;
//线程池构造
ThreadPool::ThreadPool()
:initThreadSize_(4)
, taskSize_(0)
, taskQueMaxThreshHold_(TASK_MAX_THRESHHOLD)
, poolMode_(PoolMode::MODE_FIXED)
{}
//线程池析构
ThreadPool::~ThreadPool()
{}
//设置线程池的工作模式
void ThreadPool::setMode(PoolMode mode)
{
poolMode_ = mode;
}
//设置task任务队列的上限阈值
void ThreadPool::setTaskQueMaxThreshHold(int threshhold)
{
taskQueMaxThreshHold_ = threshhold;
}
//给线程池提交任务 用户调用接口 传入任务 生产任务
void ThreadPool::submitTask(std::shared_ptr<Task>sp) //因为任务周期不知道,因此传一个智能指针
{
//获取锁
std::unique_lock<std::mutex>lock(taskQueMtx_);
//线程的通信 等待任务队列有空余
/*while (taskQue_.size() == taskQueMaxThreshHold_) //与lamda表达式意义相同
{
notFull_.wait(lock);
}*/
//用户提交任务,最长不会阻塞1s,否则判断提交任务失败,返回
if(!notFull_.wait_for(lock,std::chrono::seconds(1),
[&]()->bool {return taskQue_.size() < (size_t)taskQueMaxThreshHold_; })) //lamda表达式
{
//表示notfull_等待1s,条件依然没有满足
std::cerr << "task queue in full,submit task fail." << std::endl;
return;
}
//如果有空余,把任务放入任务队列中
taskQue_.emplace(sp);
taskSize_++;
//因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务
notEmpty_.notify_all();
}
//开启线程池
void ThreadPool::start(int initThreadSize)
{
//记录初始线程个数
initThreadSize_ = initThreadSize;
//创建线程对象
for (int i = 0; i < (int)initThreadSize_; i++)
{
//创建thread线程对象的时候,把threadpool的线程函数给到thread对象 //绑定
auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this));
//threads_.emplace_back(ptr); //unique_ptr不允许拷贝构造
threads_.emplace_back(std::move(ptr));
}
//启动所有线程
for (int i = 0; i < (int)initThreadSize_; i++)
{
threads_[i]->start();
}
}
//定义线程函数 所有线程函数是从任务队列中消费任务
void ThreadPool::threadFunc()
{
/*std::cout << "begin treadfunc tid:" << std::this_thread::get_id()
<< std::endl;
std::cout << "end treadfunc tid:" << std::this_thread::get_id()
<<std::endl;*/
for (;;)
{
std::shared_ptr<Task> task;
{
//先获取锁
std::unique_lock<std::mutex>lock(taskQueMtx_);
std::cout << "tid:" << std::this_thread::get_id()
<< "尝试获取任务" << std::endl;
//等待notEmpty条件
notEmpty_.wait(lock, [&]()->bool {return taskQue_.size() > 0; });
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();
}
}
}
线程方法实现
//线程构造
Thread::Thread(ThreadFunc func)
:func_(func)
{
}
//线程析构
Thread::~Thread()
{
}
//启动线程
void Thread::start()
{
//执行一个线程函数
std::thread t(func_); //C++11的线程类 线程对象t 和线程函数func_
t.detach(); //设置分离线程 将线程对象和线程函数进行分离,即在启动线程这个函数结束后,线程对象消失就消失了,不影响函数的运行
}
threadpool.h文件
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <vector>
#include<queue>
#include<memory>
#include<atomic>
#include<mutex>
#include<condition_variable>
#include<functional>
//任务抽象基类
class Task
{
public:
//用户可以自定义任意任务类型,从Task继承,重写run方法,实现自定义任务处理
virtual void run() = 0;
};
//线程池支持的模式
enum class PoolMode //enum class是为了解决枚举不同,但是枚举项相同的情况
{
MODE_FIXED, //固定数量的线程
MODE_CACHED, //线程数量可动态可增长
};
//线程类型
class Thread
{
public:
//线程函数对象类型
using ThreadFunc = std::function<void()>;
//线程构造
Thread(ThreadFunc func);
//线程析构
~Thread();
//启动线程
void start();
private:
ThreadFunc func_;
};
/*
example:
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);
//给线程池提交任务
void submitTask(std::shared_ptr<Task>sp); //因为任务周期不知道,因此传一个智能指针
//开启线程池
void start(int initThreadSize=4);
//禁止拷贝构造和拷贝赋值操作,用户使用线程池可以单独创建一个线程池对象,对线程池本身不能操作
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
private:
//定义线程函数
void threadFunc();
private:
std::vector<std::unique_ptr<Thread>>threads_;//threads_ 线程列表
size_t initThreadSize_; //初始的线程数量
std::queue<std::shared_ptr<Task>>taskQue_; //任务队列
std::atomic_int taskSize_; //任务数量
int taskQueMaxThreshHold_; //任务队列数量上限阈值
std::mutex taskQueMtx_; //保证任务队列的线程安全 互斥锁
std::condition_variable notFull_;
std::condition_variable notEmpty_; //表示任务队列不空
PoolMode poolMode_; //当前线程池的工作模式
};
#endif
测试文件:
#include<iostream>
#include<chrono>
#include<thread>
#include"threadpool.h"
class MyTask :public Task
{
public:
void run()
{
std::cout << "tid:" << std::this_thread::get_id()
<<"begin!" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "tid:" << std::this_thread::get_id()
<<"end!" << std::endl;
}
};
int main()
{
ThreadPool pool;
pool.start(4);
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
pool.submitTask(std::make_shared<MyTask>());
getchar();
//主线程结束后,分离线程给自动回收了,分离线程可能还没启动
//std::this_thread::sleep_for(std::chrono::seconds(5)); //睡眠函数,主线程睡眠5s
}
测试结果:
4.5 any类型
//Any类型:可以接收任意数据的类型
class Any
{
public:
Any() = default;
~Any() = default;
Any(const Any&) = delete;
Any(Any&&) = default;
Any& operator=(Any&&) = default;
template<typename T>
Any(T data) :base_(std::make_unique<Derive<T>>(data))
{}
private:
//基类类型
class Base
{
public:
virtual ~Base()=default;
};
//派生类类型
template<typename T>
class Derive :public Base
{
public:
Derive(T data) :data_(data)
{}
private:
T data_;
};
private:
//定义一个基类指针
std::unique_ptr<Base>base_;
};
4.5 Semaphone类型
//实现一个信号量类
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_++;
cond_.notify_all();
}
private:
int resLimit_;
std::mutex mtx_;
std::condition_variable cond_;
};
4.6 固定Fixed模式线程池代码如下:
cpp文件:
#include "threadpool.h"
#include<functional>
#include<thread>
#include<iostream>
const int TASK_MAX_THRESHHOLD = 1024;
//线程池构造
ThreadPool::ThreadPool()
:initThreadSize_(4)
, taskSize_(0)
, taskQueMaxThreshHold_(TASK_MAX_THRESHHOLD)
, poolMode_(PoolMode::MODE_FIXED)
{}
//线程池析构
ThreadPool::~ThreadPool()
{}
//设置线程池的工作模式
void ThreadPool::setMode(PoolMode mode)
{
poolMode_ = mode;
}
//设置task任务队列的上限阈值
void ThreadPool::setTaskQueMaxThreshHold(int threshhold)
{
taskQueMaxThreshHold_ = threshhold;
}
//给线程池提交任务 用户调用接口 传入任务 生产任务
Result ThreadPool::submitTask(std::shared_ptr<Task>sp) //因为任务周期不知道,因此传一个智能指针
{
//获取锁
std::unique_lock<std::mutex>lock(taskQueMtx_);
//线程的通信 等待任务队列有空余
/*while (taskQue_.size() == taskQueMaxThreshHold_) //与lamda表达式意义相同
{
notFull_.wait(lock);
}*/
//用户提交任务,最长不会阻塞1s,否则判断提交任务失败,返回
if(!notFull_.wait_for(lock,std::chrono::seconds(1),
[&]()->bool {return taskQue_.size() < (size_t)taskQueMaxThreshHold_; })) //lamda表达式
{
//表示notfull_等待1s,条件依然没有满足
std::cerr << "task queue in full,submit task fail." << std::endl;
return Result(sp,false);
}
//如果有空余,把任务放入任务队列中
taskQue_.emplace(sp);
taskSize_++;
//因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务
notEmpty_.notify_all();
//返回任务的Result对象
return Result(sp);
}
//开启线程池
void ThreadPool::start(int initThreadSize)
{
//记录初始线程个数
initThreadSize_ = initThreadSize;
//创建线程对象
for (int i = 0; i < (int)initThreadSize_; i++)
{
//创建thread线程对象的时候,把threadpool的线程函数给到thread对象 //绑定
auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this));
//threads_.emplace_back(ptr); //unique_ptr不允许拷贝构造
threads_.emplace_back(std::move(ptr));
}
//启动所有线程
for (int i = 0; i < (int)initThreadSize_; i++)
{
threads_[i]->start();
}
}
//定义线程函数 所有线程函数是从任务队列中消费任务
void ThreadPool::threadFunc()
{
/*std::cout << "begin treadfunc tid:" << std::this_thread::get_id()
<< std::endl;
std::cout << "end treadfunc tid:" << std::this_thread::get_id()
<<std::endl;*/
for (;;)
{
std::shared_ptr<Task> task;
{
//先获取锁
std::unique_lock<std::mutex>lock(taskQueMtx_);
std::cout << "tid:" << std::this_thread::get_id()
<< "尝试获取任务" << std::endl;
//等待notEmpty条件
notEmpty_.wait(lock, [&]()->bool {return taskQue_.size() > 0; });
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();
}
}
}
线程方法实现
//线程构造
Thread::Thread(ThreadFunc func)
:func_(func)
{
}
//线程析构
Thread::~Thread()
{
}
//启动线程
void Thread::start()
{
//执行一个线程函数
std::thread t(func_); //C++11的线程类 线程对象t 和线程函数func_
t.detach(); //设置分离线程 将线程对象和线程函数进行分离,即在启动线程这个函数结束后,线程对象消失就消失了,不影响函数的运行
}
///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任务如果没有执行完,这里会阻塞用户的线程
return std::move(any_);
}
void Result::setVal(Any any) //谁调用的
{
//存储task的返回值
this->any_ = std::move(any);
sem_.post(); //已经获取的任务的返回值,增加信号量资源
}
.h文件
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <vector>
#include<queue>
#include<memory>
#include<atomic>
#include<mutex>
#include<condition_variable>
#include<functional>
//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>
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_++;
cond_.notify_all();
}
private:
int resLimit_;
std::mutex mtx_;
std::condition_variable cond_;
};
//Task类型的前置声明
class Task;
//实现接收提交到的线程池的task任务执行完成后的返回值类型Resulit
class Result
{
public:
Result(std::shared_ptr<Task> task, bool isValid = true);
~Result() = default;
//问题1 :setVal方法,获取任务执行完的返回值的
void setVal(Any any);
//问题2: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的
};
//线程池支持的模式
enum class PoolMode //enum class是为了解决枚举不同,但是枚举项相同的情况
{
MODE_FIXED, //固定数量的线程
MODE_CACHED, //线程数量可动态可增长
};
//线程类型
class Thread
{
public:
//线程函数对象类型
using ThreadFunc = std::function<void()>;
//线程构造
Thread(ThreadFunc func);
//线程析构
~Thread();
//启动线程
void start();
private:
ThreadFunc func_;
};
/*
example:
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);
//给线程池提交任务
Result submitTask(std::shared_ptr<Task>sp); //因为任务周期不知道,因此传一个智能指针
//开启线程池
void start(int initThreadSize=4);
//禁止拷贝构造和拷贝赋值操作,用户使用线程池可以单独创建一个线程池对象,对线程池本身不能操作
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
private:
//定义线程函数
void threadFunc();
private:
std::vector<std::unique_ptr<Thread>>threads_;//threads_ 线程列表
size_t initThreadSize_; //初始的线程数量
std::queue<std::shared_ptr<Task>>taskQue_; //任务队列
std::atomic_int taskSize_; //任务数量
int taskQueMaxThreshHold_; //任务队列数量上限阈值
std::mutex taskQueMtx_; //保证任务队列的线程安全 互斥锁
std::condition_variable notFull_;
std::condition_variable notEmpty_; //表示任务队列不空
PoolMode poolMode_; //当前线程池的工作模式
};
#endif
测试文件:
#include<iostream>
#include<chrono>
#include<thread>
#include"threadpool.h"
//有些场景,是希望能够获取线程执行任务得到返回值的
//举例:
//1+。。。+3000的和
//thread1 1+..。+1000
//thread1 1000 + ... + 3000
using uLong = unsigned long long;
class MyTask :public Task
{
public:
MyTask(uLong begin, uLong end)
:begin_(begin)
, end_(end)
{}
//问题1:怎么设计run函数的返回值,可以表示任意的类型
Any run() //run函数在线程池分配的线程中去做事情
{
std::cout << "tid:" << std::this_thread::get_id()
<<"begin!" << std::endl;
//std::this_thread::sleep_for(std::chrono::seconds(2));
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.start(3);
//问题2:如何设计这里的Result机制。使其和MyTask绑定
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));
//随着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线程用来分解任务,然后给各个Salve线程分配任务
//等待各个Slave线程执行完任务,返回结果
//Master线程合并各个任务结果,输出
std::cout << (sum1 + sum2 + sum3) << std::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>());*/
getchar();
//主线程结束后,分离线程给自动回收了,分离线程可能还没启动
//std::this_thread::sleep_for(std::chrono::seconds(5)); //睡眠函数,主线程睡眠5s
}
4.7 初版线程池
4.8 初版线程池的问题
4.9解决死锁问题后的版本
cpp
#include "threadpool.h"
#include<functional>
#include<thread>
#include<iostream>
const int TASK_MAX_THRESHHOLD = 1024;
const int THREAD_MAX_THRESHHOLD = 10;
const int THREAD_MAX_IDLE_TIME = 10; //单位:秒 线程最大空闲时间
//线程池构造
ThreadPool::ThreadPool()
:initThreadSize_(4)
, taskSize_(0)
, idleThreadSize_(0)
, taskQueMaxThreshHold_(TASK_MAX_THRESHHOLD)
, threadSizeThreshHold_(THREAD_MAX_THRESHHOLD)
, curThreadSize_(0)
, 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_);
//线程的通信 等待任务队列有空余
/*while (taskQue_.size() == taskQueMaxThreshHold_) //与lamda表达式意义相同
{
notFull_.wait(lock);
}*/
//用户提交任务,最长不会阻塞1s,否则判断提交任务失败,返回
if(!notFull_.wait_for(lock,std::chrono::seconds(1),
[&]()->bool {return taskQue_.size() < (size_t)taskQueMaxThreshHold_; })) //lamda表达式
{
//表示notfull_等待1s,条件依然没有满足
std::cerr << "task queue in full,submit task fail." << std::endl;
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 threat" << std::this_thread::get_id()<< std::endl;
//创建新线程
//创建thread线程对象的时候,把threadpool的线程函数给到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_[threadId]->start();
curThreadSize_++;
idleThreadSize_++;
//threads_.emplace_back(ptr); //unique_ptr不允许拷贝构造
//threads_.emplace_back(std::move(ptr));
}
//返回任务的Result对象
return Result(sp);
}
//开启线程池
void ThreadPool::start(int initThreadSize)
{
//设置线程池的start
isPoolRunning_ = true;
//记录初始线程个数
initThreadSize_ = initThreadSize;
curThreadSize_ = initThreadSize;
//创建线程对象
for (int i = 0; i < (int)initThreadSize_; i++)
{
//创建thread线程对象的时候,把threadpool的线程函数给到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(ptr); //unique_ptr不允许拷贝构造
//threads_.emplace_back(std::move(ptr));
}
//启动所有线程
for (int i = 0; i < (int)initThreadSize_; i++)
{
threads_[i]->start();
idleThreadSize_++; //记录初始空闲线程的数量
}
}
//定义线程函数 所有线程函数是从任务队列中消费任务
void ThreadPool::threadFunc(int threadid)
{
auto lastTime = std::chrono::high_resolution_clock().now();
//所有任务必须执行完成,线程池才可以回收所有线程资源
//while (isPoolRunning_)
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,应该把多余(超过初始的线程数量)的线程结束回收掉
//当前时间-上一次线程执行的时间>60s
//每一秒中返回一次 怎么区分:超时返回?还是有任务待执行返回
while (taskQue_.size() == 0)
{
//线程池要结束,回收线程资源
if (!isPoolRunning_)
{
threads_.erase(threadid);
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_)
{
//开始回收线程
//记录线程数量的相关变量的值修改
//把线程对象从线程列表容器中删除
//threadId=>thread对象=》删除
threads_.erase(threadid);
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::cout << "threadid:" << std::this_thread::get_id() << "exit" << std::endl;
exitCond_.notify_all();
return;
}*/
}
线程池要结束、回收线程资源
//if (!isPoolRunning_)
//{
// break;
//}
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();
}
idleThreadSize_++;
lastTime = std::chrono::high_resolution_clock().now(); //更新线程执行完任务的时间
}
/*threads_.erase(threadid);
std::cout << "threadid:" << std::this_thread::get_id() << "exit" << std::endl;
exitCond_.notify_all();*/
}
bool ThreadPool::checkRunningState() const
{
return isPoolRunning_;
}
线程方法实现
int Thread::generateId_ = 0;
//线程构造
Thread::Thread(ThreadFunc func)
:func_(func)
,threadId_(generateId_++)
{
}
//线程析构
Thread::~Thread()
{
}
//启动线程
void Thread::start()
{
//执行一个线程函数
std::thread t(func_,threadId_); //C++11的线程类 线程对象t 和线程函数func_
t.detach(); //设置分离线程 将线程对象和线程函数进行分离,即在启动线程这个函数结束后,线程对象消失就消失了,不影响函数的运行
}
int Thread::getId()const
{
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任务如果没有执行完,这里会阻塞用户的线程
return std::move(any_);
}
void Result::setVal(Any any) //谁调用的
{
//存储task的返回值
this->any_ = std::move(any);
sem_.post(); //已经获取的任务的返回值,增加信号量资源
}
头文件
#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>
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_++;
cond_.notify_all();
}
private:
int resLimit_;
std::mutex mtx_;
std::condition_variable cond_;
};
//Task类型的前置声明
class Task;
//实现接收提交到的线程池的task任务执行完成后的返回值类型Resulit
class Result
{
public:
Result(std::shared_ptr<Task> task, bool isValid = true);
~Result() = default;
//问题1 :setVal方法,获取任务执行完的返回值的
void setVal(Any any);
//问题2: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的
};
//线程池支持的模式
enum class PoolMode //enum class是为了解决枚举不同,但是枚举项相同的情况
{
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:
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_;//threads_ 线程列表
std::unordered_map<int, std::unique_ptr<Thread>>threads_;//threads_ 线程列表
size_t initThreadSize_; //初始的线程数量
std::atomic_int curThreadSize_;//记录当前线程池里面线程的总数量
//记录空闲线程的数量
std::atomic_int idleThreadSize_;
int threadSizeThreshHold_;//cached模式下线程数量上限阈值
std::queue<std::shared_ptr<Task>>taskQue_; //任务队列
std::atomic_int taskSize_; //任务数量
int taskQueMaxThreshHold_; //任务队列数量上限阈值
std::mutex taskQueMtx_; //保证任务队列的线程安全 互斥锁
std::condition_variable notFull_;
std::condition_variable notEmpty_; //表示任务队列不空
std::condition_variable exitCond_; //等到线程资源全部回收
PoolMode poolMode_; //当前线程池的工作模式
//表示当前线程池的启动状态
std::atomic_bool isPoolRunning_;
};
#endif
测试文件:
#include<iostream>
#include<chrono>
#include<thread>
#include"threadpool.h"
//有些场景,是希望能够获取线程执行任务得到返回值的
//举例:
//1+。。。+3000的和
//thread1 1+..。+1000
//thread1 1000 + ... + 3000
using uLong = unsigned long long;
class MyTask :public Task
{
public:
MyTask(uLong begin, uLong end)
:begin_(begin)
, end_(end)
{}
//问题1:怎么设计run函数的返回值,可以表示任意的类型
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:
uLong begin_;
uLong end_;
};
int main()
{
{
ThreadPool pool;
//开始启动线程池
pool.start(4);
Result res1 = pool.submitTask(std::make_shared<MyTask>(1, 100000000));
uLong sum1 = res1.get().cast_<uLong>();
std::cout << sum1 << std::endl;
}
std::cout << "main over!" << std::endl;
getchar();
#if 0
//问题:ThreadPool对象析构以后,怎麽样把线程池相关的线程资源全部回收
{
ThreadPool pool;
//用户自己设置线程池的工作模式
pool.setMode(PoolMode::MODE_CACHED);
//开始启动线程池
pool.start(4);
//问题2:如何设计这里的Result机制。使其和MyTask绑定
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线程用来分解任务,然后给各个Salve线程分配任务
//等待各个Slave线程执行完任务,返回结果
//Master线程合并各个任务结果,输出
std::cout << (sum1 + sum2 + sum3) << std::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>());*/
getchar();
//主线程结束后,分离线程给自动回收了,分离线程可能还没启动
//std::this_thread::sleep_for(std::chrono::seconds(5)); //睡眠函数,主线程睡眠5s
}
#endif
}
//问题总结:不定时的运行出现死锁的情况,