之前实现了C语言版线程池,这次来实现C++版的线程池(用到了C++11的新特性)。实现整个线程池的思路可以看上期讲解,比较详细。
一:关于这个类中所需要的参数
class ZERO_ThreadPool
{
protected:
struct TaskFunc //任务的结构体
{
TaskFunc(uint64_t expireTime) : _expireTime(expireTime) //传入超时时间
{ }
std::function<void()> _func; //任务
int64_t _expireTime = 0; //超时的绝对时间
};
typedef shared_ptr<TaskFunc> TaskFuncPtr; //指向任务结构体的指针
protected:
queue<TaskFuncPtr> tasks_; //存放任务指针的结构体
std::vector<std::thread*> threads_; //存放线程的容器
std::mutex mutex_; //互斥锁
std::condition_variable condition_; //条件变量
size_t threadNum_; //线程的数量
bool bTerminate_; //标志位,标志线程是否退出
std::atomic<int> atomic_{ 0 }; //原子变量
ZERO_ThreadPool();
virtual ~ZERO_ThreadPool();
bool init(size_t num);
size_t getThreadNum(); //获得线程的数量
size_t getJobNum(); //获得现在任务的数量
void stop(); //线程退出
bool start(); // 创建线程
//这里是没有传入时间
template <class F, class... Args>
auto exec(F&& f, Args&&... args) -> std::future<decltype(f(args...))>{}
//插入任务
template <class F, class... Args>
auto exec(int64_t timeoutMs, F&& f, Args&&... args) ->std::future<decltype(f(args...))>{}
bool waitForAllDone(int millsecond = -1);
protected:
bool get(TaskFuncPtr&task); //取任务
bool isTerminate() { return bTerminate_; } //
void run();
};
二:关于重要函数的实现
1、初始化线程池。
bool ZERO_ThreadPool::init(size_t num) //初始化线程池
{
std::unique_lock<std::mutex> lock(mutex_);
if (!threads_.empty())
{
return false;
}
threadNum_ = num; // 设置线程数量
return true;
}
2、线程池的创建。
bool ZERO_ThreadPool::start()
{
std::unique_lock<std::mutex> lock(mutex_);
if (!threads_.empty())
{
return false;
}
for (size_t i = 0; i < threadNum_; i++) //创建线程
{
threads_.push_back(new thread(&ZERO_ThreadPool::run, this)); //将创建好的线程放入到容器中,并且添加线程进入的函数
}
return true;
}
3、线程池的退出。
void ZERO_ThreadPool::stop() //线程池的退出
{
{
std::unique_lock<std::mutex> lock(mutex_); //加锁
bTerminate_ = true; // 触发退出
condition_.notify_all(); //通知全部的线程
}
for (size_t i = 0; i < threads_.size(); i++)
{
if(threads_[i]->joinable())
{
threads_[i]->join(); // 等线程推出
}
delete threads_[i];
threads_[i] = NULL;
}
std::unique_lock<std::mutex> lock(mutex_);
threads_.clear();
}
4、消费者使用的函数。
bool ZERO_ThreadPool::get(TaskFuncPtr& task)
{
std::unique_lock<std::mutex> lock(mutex_); // 也要等锁
if (tasks_.empty()) // 判断是否任务存在
{
condition_.wait(lock, [this] { return bTerminate_ // 要终止线程池 bTerminate_设置为true,外部notify后
|| !tasks_.empty(); // 任务队列不为空
}); // notify -> 1. 退出线程池; 2.任务队列不为空
}
if (bTerminate_) //这里要判断一次是不是要执行退出了
return false;
if (!tasks_.empty()) //为什么要在判断一次,因为当通知之后,所有线程要在走一遍,避免两个线程同时进入
{
task = std::move(tasks_.front()); // 使用了移动语义
tasks_.pop(); // 释放一个任务
return true;
}
return false;
}
5、创建好线程池需要走的函数。
void ZERO_ThreadPool::run() // 执行任务的线程
{
//调用处理部分
while (!isTerminate()) // 判断是不是要停止
{
TaskFuncPtr task;
bool ok = get(task); // 1. 读取任务
if (ok)
{
++atomic_;
try
{
if (task->_expireTime != 0 && task->_expireTime < TNOWMS )
{
//超时任务,是否需要处理?
cout<<"你怎么超时了!"<<endl;
}
else
{
task->_func(); // 2. 执行任务
}
}
catch (...)
{
}
--atomic_;
//任务都执行完毕了
std::unique_lock<std::mutex> lock(mutex_);
if (atomic_ == 0 && tasks_.empty()) // 3.检测是否所有任务都运行完毕
{
condition_.notify_all(); // 这里只是为了通知waitForAllDone
}
}
}
}
6、对于模板函数的理解
首先 template <class F, class... Args> 这行代码:是一个模板声明的开头,它定义了一个模板,该模板可以接受一个函数类型 F
和一个可变数量的参数类型 Args...。
这种模板通常用于编写可以接受任何类型函数(包括lambda表达式、函数指针、函数对象等)和任意数量参数的通用函数或类。
这里,F
表示函数类型,而 Args...
是一个参数包,表示任意数量和类型的参数。参数包是C++11及更高版本中引入的一个特性,允许模板接收可变数量的模板参数。对于这个函数来说,接收一个超时时间、可调用对象、参数包以及返回一个future对象。其中使用了decltype来推导类型。然后通过将该任务进行包装操作。
template <class F, class... Args>
auto exec(int64_t timeoutMs, F&& f, Args&&... args) -> std::future<decltype(f(args...))>
{
int64_t expireTime = (timeoutMs ==0 ? 0: TNOWMS + timeoutMs); //now time
using RetType = decltype(f(args...)); //推到返回值
auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
TaskFuncPtr fPtr = std::make_shared<Task>(expireTime); // 封装任务指针,设置过期时间
fPtr->ptr = [task]() { // 将具体执行的任务全部封装到,这个任务的函数中去。
(*task)();
};
std::unique_lock<mutex> lock(mutex_);
task_queue.push(fPtr);
condition.notify_one();
return task->get_future();
}
此片文章主要为记录笔记,如要看详细讲解,请看上期内容。https://xxetb.xetslk.com/s/2D96kH