主要功能
提供线程来执行任务。
核心思路
-
本线程池核心构成就是任务队列,存放线程的容器,并且利用锁机制来进行对两者进行操作;
-
实现任务执行函数,由线程来进行执行;
-
利用到了C++11 的一些新特性,如
lambda表达式
、atomic原子变量
、完美转发std::forward
、std::unique_lock<>
等,可作为C++11复习的代码。
具体内容看实现代码
代码实现
ThreadPool.h
#ifndef _THREADPOOL_H_
#define _THREADPOOL_H_
#include <future>
#include <functional>
#include <iostream>
#include <queue>
#include <mutex>
#include <memory>
#include <sys/time.h>
using namespace std;
void getNow(timeval *tv);
int64_t getNowMs();
#define TNOW getNow()
#define TNOWMS getNowMs()
class ThreadPool
{
protected:
struct TaskFunc
{
TaskFunc(uint64_t expireTime) : _expireTime(expireTime)
{}
std::function<void()> _func; //函数对象类,其模板参数是函数的类型
int64_t _expireTime = 0;
};
typedef shared_ptr<TaskFunc> TaskFuncPtr;
public:
ThreadPool();
virtual ~ThreadPool();
bool init(size_t num);
size_t getThreadNum()
{
std::unique_lock<std::mutex> lock(mutex_); //构造时候使用mutex_加锁,析构时自动解锁
return threads_.size();
}
/// @brief 获取线程池任务数
size_t getJobNum()
{
std::unique_lock<std::mutex> lock(mutex_);
return tasks_.size();
}
/// @brief 停止所有线程
void stop();
/// @brief 启动所有线程
bool start();
//用线程池启用任务(F是function,Args是参数)
template <class F, class... Args>
auto exec(F &&f, Args &&...args) -> std::future<decltype(f(args...))>
{
return exec(0, f, args...);
}
//timeoutMs表示超时时间,为0表示,不做超时控制;若任务超时,此任务将被丢弃
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); //获取过期时间点
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<TaskFunc>(expireTime);
fPtr->_func = [task]() {
(*task)(); // 等同于调用f(args...)
};
std::unique_lock<std::mutex> lock(mutex_);
tasks_.push(fPtr);
//唤醒某个等待(wait)线程。如果当前没有等待线程,则该函数什么也不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。
condition_.notify_one();
return task->get_future(); // 返回任务执行后的结果
}
//等待任务队列所有工作结束
//millsecond 等待时间,-1:表示阻塞等待
//true if AllDone,false if timeout
bool waitForAllDone(int millsecond = -1);
protected:
bool getTask(TaskFuncPtr &task);
bool isTerminate() { return bTerminate_; }
void run();
protected:
//任务队列
queue<TaskFuncPtr> tasks_;
std::vector<std::thread *> threads_; //
std::mutex mutex_; //用来锁对threads_ 和 tasks_ 的操作
std::condition_variable condition_;
size_t threadNum_;
bool bTerminate_;
std::atomic<int> atomic_{0}; //
};
#endif
注解说明
-
packaged_task 、bind、forward的解释,代码如下
auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
- 当我们将一个右值引用传入函数时,他在实参中有了命名,所以继续往下传或者调用其他函数时,根据C++ 标准的定义,这个参数变成了一个左值;
std::forward
完美转发根据右值判断的推倒,调用forward 传出的值,若原来是一个右值,那么他转出来就是一个右值,否则为一个左值;std::packaged_task<>
是一个打包任务的类,模板参数是函数签名。 也很类似于std::function
,不过这个类对象不过这个类对象有get_future()
函数;- get_future()函数封装一个任务函数,返回值为RetType,参数为空;
- bind将参数args…绑定到函数f上,之后调用
task()
就等价于调用f(args...)
-
std::future
对象提供访问异步操作结果的机制,通过其中get()
函数很轻松解决从异步任务中返回结果。 -
原子变量,实现不加锁完成线程同步,效率比对数据加锁操作平均要高。
std::atomic<int> atomic_{0};
ThreadPool.cpp
#include "ThreadPool.h"
ThreadPool::ThreadPool()
:threadNum_(1), bTerminate_(false)
{
}
ThreadPool::~ThreadPool()
{
stop();
}
bool ThreadPool::init(size_t num)
{
std::unique_lock<std::mutex> lock(mutex_);
if(!threads_.empty())
{
return false;
}
threadNum_ = num;
return true;
}
void 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()) // joinable()用于检测线程是否有效
{
threads_[i]->join(); // 等待线程退出(阻塞
}
delete threads_[i];
threads_[i] = NULL;
}
std::unique_lock<std::mutex> lock(mutex_);
threads_.clear();
}
bool 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(&ThreadPool::run, this));
}
return true;
}
bool ThreadPool::getTask(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 ThreadPool::run()
{
while(!isTerminate())
{
TaskFuncPtr task;
bool ok = getTask(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();
}
}
}
}
bool ThreadPool::waitForAllDone(int millsecond)
{
std::unique_lock<std::mutex> lock(mutex_);
if(tasks_.empty()) return true;
if(millsecond < 0)
{
// 第二个参数 Predicate, 只有当pred为false时,wait才会阻塞当前线程
condition_.wait(lock, [this]
{ return tasks_.empty(); }); //任务队列不为空则阻塞。
return true;
}
else
{
//
return condition_.wait_for(lock, std::chrono::milliseconds(millsecond), [this]
{ return tasks_.empty(); });
}
}
int gettimeofday(struct timeval &tv)
{
return ::gettimeofday(&tv, 0);
}
void getNow(timeval *tv)
{
gettimeofday(*tv);
}
int64_t getNowMs()
{
struct timeval tv;
getNow(&tv);
return tv.tv_sec * (int64_t)1000 + tv.tv_usec / 1000;
}
解释说明
std::condition_variable::wait(_Lck, _Pred)
先unlock之前获得的mutex,第二个参数_Pred为false才会阻塞当前线程;
把当前线程添加到等待线程列表中,该线程会持续 block 直到被 notify_all()
或 notify_one()
唤醒。
且当 pred 为 true 时才会被解除阻塞,执行流程类似于:
while (!pred()) {
wait(lock);
}
main.cpp
测试线程池
#include "ThreadPool.h"
int testInt(int i)
{
return i;
}
void test()
{
ThreadPool tpool;
tpool.init(5);
tpool.start();
auto f = tpool.exec(testInt, 10);
cout << f.get() << endl;
tpool.waitForAllDone(1000);
tpool.stop();
}
int main()
{
test();
return 0;
}
编译调试
这里我使用CMake,也当做是复习一下
程序文件结构如下
jyhlinux@ubuntu:~/share/threadpool_cpp$ tree
.
├── build
├── CMakeLists.txt
├── main.cpp
├── ThreadPool.cpp
└── ThreadPool.h
CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
PROJECT(THREADPOOL)
ADD_EXECUTABLE(threadpool main.cpp ThreadPool.cpp)
TARGET_LINK_LIBRARIES(threadpool pthread)
编译过程
jyhlinux@ubuntu:~/share/threadpool_cpp$ cd build
jyhlinux@ubuntu:~/share/threadpool_cpp/build$ cmake ..
jyhlinux@ubuntu:~/share/threadpool_cpp/build$ make
jyhlinux@ubuntu:~/share/threadpool_cpp/build$ ./threadpool
10