c++线程池的实现
github仓库地址
线程池主要由两部组成,一是任务队列,二是线程池,任务队列存储要处理的任务,线程池分配线程去处理要处理的任务,所以我们完成整个过程分两个部分,一是维护一个线程安全的任务队列,二是创建一个线程池。
一、维护一个线程安全的任务队列
知识要点
- 互斥锁
- 条件变量
- 模板
- 万能引用加完美转发
- 虚假唤醒
源码如下:
#ifndef TASKQUE_HPP
#define TASKQUE_HPP
#include<iostream>
#include<mutex>
#include<queue>
#include<condition_variable>
#include<assert.h>
template<typename T>
class TaskQue
{
public:
TaskQue():ifstop(false){}
template<typename U>//相当于模板函数独立出来了
void push(U&&item)//万能引用
{
{
// static_assert(std::is_same<T,U>::==true);//判断模板类型是否一样
std::lock_guard<std::mutex>locker(m_mutex);
m_queue.push(std::forward<U>(item));//完美转发 保证之传进来的参数类型的属性(左值右值等等)
}
m_cond.notify_all();
}
bool pop(T&item)
{
std::unique_lock<std::mutex>locker(m_mutex);
while(m_queue.empty()||ifstop)
{
m_cond.wait(locker);
}
if(m_queue.empty())
return false;
item=(std::move(m_queue.front()));
m_queue.pop();
return true;
}
size_t size()const
{
std::lock_guard<std::mutex>locker(m_mutex);
return m_queue.size();
}
bool empty()
{
std::lock_guard<std::mutex>locker(m_mutex);
return m_queue.empty();
}
void stop()
{
{
std::lock_guard<std::mutex>locker(m_mutex);
ifstop=true;
}
m_cond.notify_all();
}
private:
std::mutex m_mutex;
std::queue<T> m_queue;
std::condition_variable m_cond;
bool ifstop;//c++11特性
};
#endif
说明:
1.其中类中push方法运用函数模板实现万能引用,使之传入进来的对象可以是任意类型的并且是任意属性的(左值或者右值),利用完美转发可以实现传进来的参数保留其参数原来的属性。
2.在pop方法中当队列为空或者停止条件为true的时候使用条件变量使线程阻塞,减少线程资源的消耗
3.由于任务队列是线程中的公共资源,所以每个方法中使用互斥锁使共享资源保持完整性。
二、创建一个线程池
知识要点:
- 函数重载
- 智能指针
- function多态函数包装器的使用
- future期物的使用(异步提供)
- 函数后置返回类型(auto和decltype的混合使用)
- 多参函数模板的使用
源码如下:
#ifndef THREADPOOL_HPP
#define THREADPOOL_HPP
#include<memory>
#include<iostream>
#include<queue>
#include<thread>
#include<vector>
#include<mutex>
#include<condition_variable>
#include<functional>
#include<future>
#include <utility>
#include"TaskQue.hpp"
using namespace std;
class ThreadPool
{
public:
explicit ThreadPool(const int threads=4):work_threads(vector<thread>(threads)),m_shutdown(false){
std::cout<<"pool begin"<<std::endl;
// std::thread();
}
void init()//初始化线程池
{
for(int i=0;i<work_threads.size();++i)
{
work_threads[i]=thread(Work(this,i));//分配工作线程
}
}
void shutdown()//关闭线程池
{
m_shutdown=true;
cond.notify_all();
for(int i=0;i<work_threads.size();++i)
{
if(work_threads[i].joinable())
{
work_threads[i].join();
}
}
std::cout<<"shut down"<<std::endl;
}
//禁止拷贝构造和移动语义
ThreadPool(const ThreadPool &) = delete;
ThreadPool(ThreadPool &&) = delete;
ThreadPool &operator=(const ThreadPool &) = delete;
ThreadPool &operator=(ThreadPool &&) = delete;
template<typename F,typename... Args>//多参数的函数模板
auto submit(F&&f,Args&& ...args)->std::future<decltype(f(args...))>//多参数的函数模板化
{
std::function<decltype(f(args ...))()>func=std::bind(std::forward<F>(f),std::forward<Args>(args) ...);//避免左右值的歧义
//用bind进行包装绑定 调用的时候就不需要调用参数
auto taskptr=make_shared<packaged_task<decltype(f(args ...))()>>(func);//创建了一个智能指针 std::packaged_task的绑定构造 用于期物的创建
// auto taskptr=make_shared<packaged_task<decltype(f(args ...)())>>(std::bind(std::forward<F>(f),std::forward<Args>(args) ...));
function<void()> task_func=[taskptr](){
(*taskptr)();
};//任意类型的返回值
tasks.push(task_func);//压入任务队列
cond.notify_one();//唤醒其中的一个线程
return taskptr->get_future();//返回一个期物 用于在不同线程调用函数返回值
}
~ThreadPool()
{
shutdown();//线程池析构的时候进行关闭
}
private:
class Work{
public:
Work(ThreadPool*pool,int id):m_pool(pool),m_id(id){//初始化工作线程
cout<<"work id is "<<m_id<<endl;
}
void operator()()//重载()开始工作
{
//基础任务函数
std::function<void()>task;
//是否正在进行出队
bool dequeued;
while(!m_pool->m_shutdown)
{
{
std::unique_lock<std::mutex>locker(m_pool->m_mutex);
while(m_pool->tasks.empty())
{
std::cout<<m_id<<" is blocked"<<std::endl;//阻塞
m_pool->cond.wait(locker);
}
std::cout<<"be waked id is "<<m_id<<std::endl;
dequeued=m_pool->tasks.pop(task);
}
if(dequeued)
{
task();//执行任务
}
}
}
private:
int m_id; //工作线程的id
ThreadPool *m_pool;//所属的线程池
};
TaskQue<std::function<void()>>tasks;//std::function 用于包装函数
vector<thread>work_threads;
condition_variable cond;
mutex m_mutex;
bool m_shutdown;
};
#endif
说明:
1、初始化线程池,使用内部类work重载“()”使之变为函数对象实现线程的工作,若是任务队列为空线程就使用条件变量阻塞。
2、submit函数结合auto和decltype使用函数后置返回类型,返回类型是一个期物,为了在其他线程中得到该线程的状态(状态的共享)。
3、submit任务提交函数,使用了多餐函数模板,使函数对象的参数可以任意任意参数,并且可以接受lambda表达式。
4、submit函数中先使用function包装了传进来的函数,使用智能指针在包装一层(为了在另一个线程中返回期物,防止线程间资源的泄漏)。
5、使用function<void()>类型为无返回类型的无参数的函数对象的function对象对任务函数进行最后的包装,利用lambda进行传递真正的任务函数,实现多态擦除函数类型,将任务函数以function<void()>对象的形式传入任务队列,实现了对任务函数的多态化的兼容。
6、shutdown函数实现对线程池的关闭,利用条件变量唤醒所有的线程立刻完成当前的工作,调用join实现线程的回收。线程调用析构函数时候会先判断线程是否处于可连结状态(joinable),若处于可连接状态,线程将不会被资源回收,调用join可以实现线程脱离可连接状态。
7、要避免虚假唤醒的问题,因为操作系统的不确定性,他可能会在无意中执行唤醒原语将线程唤醒,而此时其实任务队列中的并没有任务,这种情况就叫虚假唤醒,解决方法是:将 if(m_pool->tasks.empty())中的 if 改为 while 因为这样处理能再一次检查队列的情况以判断是虚假唤醒还是真是唤醒,确保后续的逻辑不会出错。
以上就是对线程池的理解及代码,代码不多重在对c++11语法糖的理解。
另外附上github源代码地址:
github仓库地址