代码来自 腾讯课堂《零声学院C/C++架构师课程》
腾讯课堂 零声教育 ke.qq.com/course/4209… 柚子老师: 2690491738
代码
跟上一篇的C语言实现的线程池不同,该版本使用C++11实现,使用了很多C++11中的新特性,包括任务的异步返回,移动语义,万能引用,完美转发以及可变参数模板等。这里主要分析如何通过线程池启动任务,对于线程池的创建和和退出此类功能这里不做介绍。
运行方式
- ZERO_ThreadPool tpool;
- tpool.init(5); //初始化线程池线程数
- //启动线程方式
- tpool.start();
- //将任务丢到线程池中
- tpool.exec(testFunction, 10);
- //等待线程池结束
- tpool.waitForAllDone(1000); //参数<0时, 表示无限等待
- //外部需要结束线程池时调用
- tpool.stop();
推送任务到线程池
// 任务节点的定义,线程池中对超时任务做了单独处理,不考虑超时就等于是一个函数对象
struct TaskFunc
{
TaskFunc(uint64_t expireTime) : _expireTime(expireTime)
{ }
std::function<void()> _func;
int64_t _expireTime = 0;
};
typedef shared_ptr<TaskFunc> TaskFuncPtr; // 使用智能指针管理,获取一个任务计数+1,执行结束自动销毁
// 万能引用 F&& f :根据传入参数的值性来决定是右值引用还是左值引用
// 可变模板参数class... Args
// F是函数对象, Args是函数的参数列表
// -> std::future<> 的意思是返回值是一个std::future类型,写法与lambda表达式类似
// decltype(f(args...) 通过表达式对参数为 ‘args...’的函数‘f’返回值类型进行推导
// decltype功能于auto类似,但是对数组、引用、const等特性的支持更好,能使用auto的地方都可以使用decltype,反之未必
//decltype中需要传入参数本身而不是类型,也就是说decltype(f(args...))是一个表达式
template <class F, class... Args>
auto exec(F&& f, Args&&... args) -> std::future<decltype(f(args...))>
{
return exec(0,f,args...);
}
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);
// 定义类型的别名,效果类似typedef定义别名,但是更简洁
// typedef typename decltype(f(args...))::RetType RetType;
using RetType = decltype(f(args...));
//std::make_shared 智能指针shared_ptr的标准构造方式
// std::packaged_task<RetType()>>(std::function obj) 封装任务
// std::bind绑定一个可调用对象,同时可以绑定其传入参数,返回一个可调用对象
// std::forward 与万能引用配合使用,转发参数时保留其值别特征
auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
// 封装任务指针,使用一个lambda表达式去执行实际任务
TaskFuncPtr fPtr = std::make_shared<TaskFunc>(expireTime);
fPtr->_func = [task]() {
(*task)(); // 执行
};
// 将封装好的任务任务指针添加到队列中,并唤醒一个线程去执行
std::unique_lock<std::mutex> lock(_mutex);
_tasks.push(fPtr);
_condition.notify_one();
// std::packaged_task返回的是一个void类型,需要通过get_future得到std::future对象
return task->get_future();
}
- exec()返回的是一个std::future对象,用于异步获取事件的返回值。
- 采用可变模板参数作为绑定对象的参数列表,使该接口成为一个通用接口,不限制参数类型和数量
执行任务的线程
bool ZERO_ThreadPool::get(TaskFuncPtr& task)
{
std::unique_lock<std::mutex> lock(_mutex);
if (_tasks.empty())
{
_condition.wait(lock, [this] { return _bTerminate || !_tasks.empty(); });
}
if (_bTerminate)
return false;
if (!_tasks.empty())
{
task = std::move(_tasks.front()); // 使用了移动语义(浅拷贝实现内存资源转移)
_tasks.pop();
return true;
}
return false;
}
void ZERO_ThreadPool::run() // 执行任务的线程
{
//调用处理部分
while (!isTerminate()) // 判断是不是要停止
{
TaskFuncPtr task;
bool ok = get(task); // 读取任务
if (ok)
{
++_atomic;
try
{
if (task->_expireTime != 0 && task->_expireTime < TNOWMS )
{
//超时任务,是否需要处理?
}
else
{
task->_func(); // 执行任务
}
}
catch (...)
{
}
--_atomic;
//任务都执行完毕了
std::unique_lock<std::mutex> lock(_mutex);
if (_atomic == 0 && _tasks.empty())
{
_condition.notify_all(); // 这里只是为了通知waitForAllDone
}
}
}
}