C++实现线程池
1、背景
多线程编程中,有很多线程处理同样的任务,频繁的创建和销毁线程对系统开销、性能都不友好。利用生产者消费者的思想的,可以把要处理的任务放到一个队列缓冲区里,主线程往缓冲区中队列中添加任务,而工作线程则从队列中取出任务来处理。利用池化思想,就可以把工作线程初始化为一个池,池里有一定数量的工作线程,互斥的从任务队列中取出任务处理。
2、应用场景
3、实现方式
两种实现方式,因为多线程涉及到线程间的互斥和同步,这里采取两种实现方式,实现方式一采用Linux提供的互斥锁和信号量。方式二采用C++11提供的互斥和同步的机制。
方式一,基于Linux提供的互斥锁和信号量来实现。
class handler_t{
public:
bool operator()(T data){
process(data);
}
};
class ThreadTask
{
private:
int _data; //要传入的数据
handler_t _handler; //处理数据的方法
public:
void Run()
{
_handler(data);
}
}
#define MAXCAP 3
class BlockQueue
{
private:
std::queue<ThreadTask> q;
size_t cap;
pthread_mutex_t mutex;
pthread_cond_t Cus_cond;
pthread_cond_t Pro_cond;
public:
BlockQueue(size_t c = MAXCAP):cap(c)
{
//mutex=PTHREAD_MUTEX_INITIALIZER; //C++不用这个
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&Cus_cond,NULL);
pthread_cond_init(&Pro_cond,NULL);
}
~BlockQueue()
{
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&Cus_cond);
pthread_cond_destroy(&Pro_cond);
}
bool push(const ThreadTask& data)
{
//要往缓冲区队列放数据,就得先判断缓冲区是否满了,所有线程都可以访问缓冲区,那么缓冲区就是临界资源
//需要进行保护
pthread_mutex_lock(&mutex);
//循环条件防止时间片调度产生的访问出错
while(q.size() == cap)
{
pthread_cond_wait(&Pro_cond,&mutex);//解锁阻塞加锁
}
q.push(data);
pthread_cond_signal(&Cus_cond);
pthread_mutex_unlock(&mutex);
return true;
}
bool pop(ThreadTask* data)
{
pthread_mutex_lock(&mutex);
while(q.empty() == true)
{
pthread_cond_wait(&Cus_cond,&mutex);
}
*data= q.front();
q.pop();
pthread_cond_signal(&Pro_cond);
pthread_mutex_unlock(&mutex);
return true;
}
};
#define MAX_THREAD 5
#define MAX_QUEUE 10
class threadpool
{
private:
int _max_thread;
BlockQueue _queue;
private:
//这个入口函数如果在类中声明,就会自动带有this指针,而入口函数的参数返回值是固定的,将他定义为static
//就可以解决,但static后就变成了全局的,相当于每个线程调的都是这一个入口函数,也就达不到目的了
//将this通过参数传进来就可以解决问题
static void* th_entry(void* arg)
{
threadpool*pb = (threadpool*)arg;
while(1)
{
ThreadTask task;
pb->_queue.pop(&task); //获取到节点
task.Run();
}
return NULL;
}
public:
threadpool(int maxt = MAX_THREAD,int maxq = MAX_QUEUE):_max_thread(maxt),_queue(maxq)
{
int ret;
pthread_t tid[];
for(int i = 0; i<_max_thread; ++i)
{
ret = pthread_create(&tid[i],NULL,th_entry,this);
if(ret != 0)
{
exit(-1); //构造函数没法判断成功与否,出问题就直接退出进程
}
pthread_detach(tid[i]);
}
}
bool TaskPush(const ThreadTask& task)
{
return _queue.push(task);
}
};
方式二,使用C++11的线程库。
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <mutex>
#include <condition_variable>
#include <queue>
#include <thread>
#include <functional>
class ThreadPool {
public:
explicit ThreadPool(size_t threadCount = 8): pool_(std::make_shared<Pool>()) {
assert(threadCount > 0);
for(size_t i = 0; i < threadCount; i++) {
std::thread([pool = pool_] {
std::unique_lock<std::mutex> locker(pool->mtx);
while(true) {
if(!pool->tasks.empty()) {
auto task = std::move(pool->tasks.front());
pool->tasks.pop();
locker.unlock();
task();
locker.lock();
}
else if(pool->isClosed) break;
else pool->cond.wait(locker);
}
}).detach();
}
}
ThreadPool() = default;
ThreadPool(ThreadPool&&) = default;
~ThreadPool() {
if(static_cast<bool>(pool_)) {
{
std::lock_guard<std::mutex> locker(pool_->mtx);
pool_->isClosed = true;
}
pool_->cond.notify_all();
}
}
template<class F>
void AddTask(F&& task) {
{
std::lock_guard<std::mutex> locker(pool_->mtx);
pool_->tasks.emplace(std::forward<F>(task));
}
pool_->cond.notify_one();
}
private:
struct Pool {
std::mutex mtx;
std::condition_variable cond;
bool isClosed;
std::queue<std::function<void()>> tasks;
};
std::shared_ptr<Pool> pool_;
};
#endif //THREADPOOL_H
两种方式需要注意的一点是,线程回调函数的区别,方式一使用普通的phtread_create,作为回调的成员函数要声明为static,避免多传一个this指针。方式二使用了一个lambda表达式来作为线程的一个回调函数。