目录
1 什么是线程池
线程池(thread pool):一种线程使用模式。线程池维护了多个已经创建好的线程。当有大量短时任务需要处理时,不用一个个创建进程去执行任务。避免了频繁创建销毁线程的消耗,同时也提高了任务处理的效率。
原理:在线程池中创建并初始化固定数量的线程。使用一个阻塞队列作为任务队列,利用锁和条件变量保证接任务和取任务之间的互斥和同步。
2 普通模式
在InitThreadPool函数中创建多个进程,每个进程都在互斥且同步地争夺任务队列中的任务并处理。
#pragma once
#include <iostream>
#include <queue>
namespace ns_pthreadpool
{
const int g_num = 5;
template<class T>
class ThreadPool
{
public:
ThreadPool(int num = g_num)
:_num(num)
{
pthread_mutex_init(&_mtx,nullptr);
pthread_cond_init(&_cond,nullptr);
}
static void* Rountine(void* args)
{
pthread_detach(pthread_self());
ThreadPool<T>* tp = (ThreadPool<T>*)args;
while(true)
{
tp->Lock();
while(tp->empty())
{
tp->Wait();
}
T t;
tp->PopTask(&t);
tp->Unlock();
t();//处理任务
}
}
void InitThreadPool()
{
pthread_t tid;
for(int i=0;i<_num;i++)
{
pthread_create(&tid,nullptr,Rountine,(void*)this);
}
}
~ThreadPool()
{
pthread_mutex_destroy(&_mtx);
pthread_cond_destroy(&_cond);
}
void PushTask(T& in)
{
Lock();
_task_queue.push(in);
Unlock();
Wakeup();
}
void PopTask(T* out)//在上锁区内调用,里面不用再上锁
{
*out = _task_queue.front();
_task_queue.pop();
}
private:
void Lock()
{
pthread_mutex_lock(&_mtx);
}
void Unlock()
{
pthread_mutex_unlock(&_mtx);
}
void Wait()
{
pthread_cond_wait(&_cond,&_mtx);
}
void Wakeup()
{
pthread_cond_signal(&_cond);
}
bool empty()
{
return _task_queue.empty();
}
int _num;
std::queue<T> _task_queue;
pthread_mutex_t _mtx;
pthread_cond_t _cond;
};
} // namespace ns_pthread_pool
这里的线程执行函数要设置为静态成员函数,是因为线程执行函数只能传递一个void*类型的参数,如果是普通成员函数,第一个参数是隐藏的this指针,就没有办法接收到线程传的参数。于是该线程执行函数要改成是静态的。
static void* Rountine(void* args)
{
pthread_detach(pthread_self());
ThreadPool<T>* tp = (ThreadPool<T>*)args;
while(true)
{
tp->Lock();
while(tp->empty())
{
tp->Wait();
}
T t;
tp->PopTask(&t);
tp->Unlock();
t();//处理任务
}
}
3 单例模式
某些类, 只应该具有一个对象(实例), 就称之为单例。线程池的创建也应该只有一次,于是我们把上面的普通模式的线程池改为单例模式。
#pragma once
#include <iostream>
#include <queue>
namespace ns_pthreadpool
{
const int g_num = 5;
template <class T>
class ThreadPool
{
private:
//单例下构造函数私有
ThreadPool(int num = g_num)
: _num(num)
{
pthread_mutex_init(&_mtx, nullptr);
pthread_cond_init(&_cond, nullptr);
}
ThreadPool(const ThreadPool<T>& tp) = delete;
ThreadPool<T>& operator=(ThreadPool<T>& tp) = delete;
static ThreadPool<T>* inst;//饿汉模式的标志位
public:
static ThreadPool<T>* GetInstance()
{
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
if(inst == nullptr)//双判定,先满足条件再抢锁
{
pthread_mutex_lock(&lock);
if(inst == nullptr)
{
inst = new ThreadPool<T>();
inst->InitThreadPool();
std::cout<<"首次加载对象"<<std::endl;
}
pthread_mutex_unlock(&lock);
}
return inst;
}
static void *Rountine(void *args)
{
pthread_detach(pthread_self());
ThreadPool<T> *tp = (ThreadPool<T> *)args;
while (true)
{
tp->Lock();
while (tp->empty())
{
tp->Wait();
}
T t;
tp->PopTask(&t);
tp->Unlock();
t(); //处理任务
}
}
void InitThreadPool()
{
pthread_t tid;
for (int i = 0; i < _num; i++)
{
pthread_create(&tid, nullptr, Rountine, (void *)this);
}
}
~ThreadPool()
{
pthread_mutex_destroy(&_mtx);
pthread_cond_destroy(&_cond);
}
void PushTask(T &in)
{
Lock();
_task_queue.push(in);
Unlock();
Wakeup();
}
void PopTask(T *out) //在上锁区内调用,里面不用再上锁
{
*out = _task_queue.front();
_task_queue.pop();
}
private:
void Lock()
{
pthread_mutex_lock(&_mtx);
}
void Unlock()
{
pthread_mutex_unlock(&_mtx);
}
void Wait()
{
pthread_cond_wait(&_cond, &_mtx);
}
void Wakeup()
{
pthread_cond_signal(&_cond);
}
bool empty()
{
return _task_queue.empty();
}
int _num;
std::queue<T> _task_queue;
pthread_mutex_t _mtx;
pthread_cond_t _cond;
};
template<class T>
ThreadPool<T>* ThreadPool<T>::inst = nullptr;
} // namespace ns_pthread_pool
增加了饿汉模式的标志位,对是否是第一次创建线程池进行判断。如果只有单判断,先上锁再判断的话,每个线程都要先抢锁才能判断消耗太大。而先判断再枪锁,整个过程不满足原子性。双判读则可以保证在抢锁时是一定满足条件的,不满足条件的线程就不需要去抢锁了。
static ThreadPool<T>* inst;//饿汉模式的标志位
static ThreadPool<T>* GetInstance()
{
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
if(inst == nullptr)//双判定,先满足条件再抢锁
{
pthread_mutex_lock(&lock);
if(inst == nullptr)
{
inst = new ThreadPool<T>();
inst->InitThreadPool();
std::cout<<"首次加载对象"<<std::endl;
}
pthread_mutex_unlock(&lock);
}
return inst;
}