线程池
为什么要使用线程池
池化技术一直是一种高效的技术,我们都知道线程的合理使用可以将CPU的性能发挥到最大,对于使用线程池有以下优点:
- 每个进程不用动态开辟线程,节约了时间成本
- 线程池里面的线程可以被不同进程共享,而进程自己开辟的的线程不能进程间共享
- 如果每个线程都开辟大量线程,因为创建线程需要系统调用,而线程间的切换也需要系统的调用,会大大小号CPU的性能,而这些工作交给线程池统一管理是十分高效的
线程池设计思路
- 线程池要设计成单例模式,系统在初始化的时候生成线程池,一个系统里面一个线程池
- 线程池中线程的数量最好设计成CPU的核数达到并行的效果
- 线程池的结构可以设计成两种:阻塞队列 和 环形队列 具体原理参考:线程
核心思路:参考生产者消费者模型,这里的生产者是系统内的进程,这些进程向线程池中的线程(消费者)派发任务,消费者拿到这些任务并执行
任务应该包括哪些内容?
应该包括:输入数据、解决方法、输出数据
这些必须都传给线程池中的线程才行,线程池中的线程从任务中拿取输入数据调用解决方法,最后把输出结果写入,线程池不需要对任务做任何多余的操作!理解这一点很重要。而这个方法函数叫做——回调函数
思路一:阻塞队列
结构体中的对象包括👇
std::vector<pthread_t> pool;
std::queue<Task *> task_queue; //任务队列
pthread_cond_t cond;
pthread_mutex_t mutex;
大体思路是:任务队列不为空就通过条件变量向线程池中的线程发送信号执行任务,如果任务队列为空,则线程池中的线程在cond条件变量下等待。
代码如下:
// pthread_getconcurrency //获取线程的最大并发数
#include <pthread.h>
#include <vector>
#include <queue>
#include <mutex>
#include <iostream>
#include <thread>
namespace sht
{
class Task // 所有的任务都必须从这个类里面继承
{
public:
virtual void operator()() = 0;
};
class ThreadPool
{
public:
static void *fun(void *args)
{
ThreadPool *p = (ThreadPool *)args;
while (true)
{
pthread_mutex_lock(&(p->mutex));
while (p->task_queue.empty())
{
pthread_cond_wait(&(p->cond), &(p->mutex));
}
Task *mission = p->task_queue.front();
p->task_queue.pop();
pthread_mutex_unlock(&(p->mutex));
// 执行任务
(*mission)();
}
}
ThreadPool()
{
// 获取硬件的最大并发数
int sz = std::thread::hardware_concurrency();
std::cout << sz << std::endl;
pool.resize(sz);
for (int i = 0; i < sz; i++)
pthread_create(&pool[i], nullptr, fun, (void *)this);
pthread_cond_init(&cond, nullptr);
pthread_mutex_init(&mutex, nullptr);
}
void push(Task *p)
{
pthread_mutex_lock(&mutex);
task_queue.push(p);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return;
}
private:
std::vector<pthread_t> pool;
// 这里我们实现一个阻塞队列
std::queue<Task *> task_queue;
pthread_cond_t cond;
pthread_mutex_t mutex;
};
}
注意
- fun函数是线程的调用函数在类内必须声明成static成员函数,不然会带有this指针,但是声明成static成员函数问题也随之而来没有this指针该如何调用类的其他成员呢?解决方法也很简单,把this指针当做参数传入fun函数中。再在fun函数里面强转一下就可以得到this指针
- 我这里把任务设计成了一个纯虚类,目的是为了将任务的解决方法固定为函数
void operator ()()
,仅此而已,如果你不想使用纯虚类,也可以把线程池设计成一个模板,把任务当成模板插入线程池
不足
- 阻塞队列并没有完成生产者(向线程池发布任务的人)和消费者(线程里面执行任务的线程)的完全解耦
- 没有设计成单例模式
思路二:环形队列
结构体中的对象包括👇
std::vector<pthread_t> pool;
sem_t sem; // 信号量
pthread_mutex_t mutex; // 互斥锁
std::list<task *> task_queue; // 任务队列
bool flag = true; // 终止线程池
大体思路是: 把信号量的大小设置成任务队列task_queue的大小,这样临界资源的粒度就变小了,实现了生产和消费的解耦
模拟实现
#include <pthread.h>
#include <vector>
#include <list>
#include <semaphore.h>
#include <thread>
// 反应堆式 线程池
// 需要实现单例模式
// 需要用信号量实现
namespace sht
{
class task
{
public:
virtual void operator()() = 0;
};
class ThreadPool
{
public:
static void *fun(void *args)
{
ThreadPool *_this = static_cast<ThreadPool *>(args);
task *mission = nullptr;
while (_this->flag)
{
// std::cout << "p" << (_this->task_queue).size() << std::endl;
sem_wait(&(_this->sem));
// 判断任务队列不能为0
if ((_this->task_queue).size() != 0)
{
pthread_mutex_lock(&(_this->mutex));
if ((_this->task_queue).size() != 0)
{
mission = (_this->task_queue).front();
(_this->task_queue).pop_front();
}
pthread_mutex_unlock(&(_this->mutex));
// 执行任务
(*mission)();
}
}
return nullptr;
}
void push_task(task *t)
{
pthread_mutex_lock(&mutex);
task_queue.push_back(t);
// std::cout << task_queue.size() << std::endl;
sem_post(&sem);
pthread_mutex_unlock(&mutex);
}
static ThreadPool *get_threadpool()
{
if (singleton == nullptr)
{
pthread_mutex_lock(&pool_mutex);
if (singleton == nullptr)
{
singleton = new ThreadPool;
}
pthread_mutex_unlock(&pool_mutex);
}
return singleton;
}
static void destroy_threadpool()
{
if (singleton != nullptr)
{
pthread_mutex_lock(&pool_mutex);
if (singleton != nullptr)
{
singleton->~ThreadPool();
}
pthread_mutex_unlock(&pool_mutex);
}
}
private:
ThreadPool()
{
// std::cout << std::thread::hardware_concurrency() << std::endl;
pool.resize(std::thread::hardware_concurrency());
sem_init(&sem, 0, pool.size());
pthread_mutex_init(&mutex, nullptr);
for (int i = 0; i < pool.size(); i++)
{
pthread_create(&pool[i], nullptr, fun, (void *)this);
}
}
~ThreadPool()
{
flag = true;
pthread_mutex_destroy(&mutex);
sem_destroy(&sem);
}
static ThreadPool *singleton;
static pthread_mutex_t pool_mutex;
std::vector<pthread_t> pool;
sem_t sem; // 信号量
pthread_mutex_t mutex; // 互斥锁
std::list<task *> task_queue; // 任务队列
bool flag = true; // 终止线程池
};
ThreadPool *ThreadPool::singleton = nullptr;
pthread_mutex_t ThreadPool::pool_mutex = PTHREAD_MUTEX_INITIALIZER;
} // namespace sht
最后线程池被设计成了单例模式,可以保证该线程池全局唯一