什么是线程池?
所谓线程池,顾名思义就是一个关于线程的数据结构;线程池是由服务器预先创建的一组子线程,线程池中的线程数量应该和CPU数量差不多。线程池中的所有子线程都运行着相同的代码。当有新的任务到来的时候,主线程将通过某种方式选择线程池的某一个子线程来为之服务。
选择方式:
主线程使用某种算法主动选择子线程。最简单又最常用的算法是随机算法和Round Robin(轮流选取)算法
主线程和所有子线程通过一个共享的工作队列来同步,子线程都睡眠在该工作队列上。当有新的任务到来的时候,主线程将任务添加到工作队列中。这将唤醒正在等待任务的子线程,不过只有一个子线程可以拿到任务的“接管权”,其他子线程只能继续睡眠等待有新的任务的到来
为什么要用线程池,创建线程池的好处是什么?
首先我们知道,线程是CPU分配资源的最小单位,虽然是最小的,但是每次的创建线程或者销毁线程都需要浪费CPU资源,那样的服务器在每次有一个任务到来的时候都通过创建一个新的子线程来处理任务,处理完后就销毁,会浪费很多的CPU资源,所以我们设想预先创建好一些进程,每次有任务到来的时候就唤醒其中一个线程区执行任务,其他线程继续等待,执行完任务的线程并不会销毁,而是睡眠。这样就避免了CPU资源的大量浪费,因为线程已经被创建好了,需要的时候直接拿出来用,不需要转到内核态再去创建,不需要的时候也可以直接睡眠。
线程池里的线程数量应该是多少?
我们创建线程池的时候不要创建过多的线程,如果创建太多线程,就体现不出线程池的优势了,但是也不能之创建1,2个线程,那样没办法处理高并发的连接,那么线程池里面的线程数量应该是多少呢?
线程数量与CPU,IO,并行,并发这些因素都有关
CPU密集型应用:CPU的数目+1
IO密集型应用:2*CPU的数目+1
最佳线程数目=(线程等待时间与线程CPU时间之比+1)*CPU数目
如何设计一个线程池呢?
线程池有点类似生产者-消费者模型,每次生产者生产出产品(即客户端发来请求任务),都会通知消费者去消费(子线程进行请求任务的处理),所以设计步骤如下:
设置一个生产者消费者队列,作为临界资源(全局资源)
初始化n个线程,并让其运行起来,加锁去队列里面取任务进行处理
如果任务队列为空的话,线程就阻塞
当生产者队列有一个任务后,就先对队列加锁,然后使用条件变量去通知阻塞队列中的一个线程来处理
代码
首先需要条件变量,信号量和锁,于是我们创建一个类(创建在lock.h中)来包含这些。
//线程同步机制封装类
#include <iostream>
using namespace std;
#include <pthread.h>
//互斥锁类
class Locker()
{
public:
//构造函数初始化锁
Locker()
{
pthread_mutex_init(&mutex,null);
}
//上锁
void Lock()
{
pthread_mutex_lock(&mutex);
}
//解锁
void Unlock()
{
pthread_mutex_unlock(&mutex);
}
//析构函数销毁锁
~Locker()
{
pthread_mutex_destroy(&mutex);
}
private:
pthread_mutex_t mutex;
};
//条件变量类
class Cond
{
public:
//构造函数初始化条件变量
Cond()
{
pthread_cond_intit(&cond,null);
}
//析构函数摧毁条件变量
~Cond()
{
pthread_cond_destroy(&cond);
}
//阻塞函数,调用后线程就会阻塞
void Wait(othread_mutex_t *mutex)
{
pthread_cond_wait(&cond,mutex);
}
//唤醒一个或者多个线程
void Signal()
{
pthread_cond_signal(&cond);
}
private:
pthread_cond_t cond;
};
//信号量类
class Sem
{
public:
//构造函数初始化信号量
Sem(int num)
{
sem_init(&sem,0,num);
}
//析构函数摧毁信号量
~Sem()
{
sem_destroy(&sem);
}
//调用该函数,信号量的值-1,并对信号量加锁,如果信号量=0,则线程阻塞,信号量!=0就会解除阻塞
void Wait()
{
sem_wait(&sem);
}
//调用该函数,对信号量解锁并且信号量的值+1
void Post()
{
sem_post(&sem);
}
private:
sem_t sem;
};
创建好线程池中维护数据安全的类后就可以开始实现线程池了
#include "lock.h"
#include <queue.h>
#include <exception>
//运用模板类,这样可以处理不同的业务
template <class T>
class pthread_pool
{
public:
//构造函数,不用默认构造,初始化线程池
pthread_pool(int num=8,int max_queue=1000);
//析构函数
~pthread_pool();
//添加任务函数
bool Append();
private:
static void *worker(void *arg);//静态函数(只能操作静态成员),工作函数,不断地从任务队列中取出任务进行处理
void run();//运行线程池
private:
int m_num; //线程数量
pthread_t *m_pthread; //线程池是一个数组,数组的大小就是m_num
int m_max_queue; //任务队列最多能容纳的任务数量
queue<T *> q; //任务队列
Lock lock; //锁
Sem sem; //信号量来判断任务队列是否有数据需要被线程处理
bool stop; //判断线程池是否关闭,false开始,true为开启
};
//用初始化列表来初始化线程池
template <class T>
pthread_pool<T>::pthread_pool(int num,int max_queue):m_num(num),m_max_queue(max_queue),stop(false),m_pthread(NULL)
{
if((num<=0)||(max_queue<=0))
{
//抛出错误
throw std::exception();
}
//创建数组
m_pthread=new pthread_t[num];
//创建线程,并且设置为线程分离(因为主线程不负责回收子线程资源这个任务)
for(int i=0;i<num;i++)
{
pthread_create(m_pthread+i,NULL,worker,this);//worker函数是一个指针函数,用来处理任务的,因为最后一个参数是要传递给worker的参数,因为线程需要用到线程池中的函数,所以传入this
pthread_detach(m_pthread[i]);
}
}
//释放线程池资源
template<class T>
pthread_pool<T>::~pthread_pool()
{
delete []m_pthread;
stop=true;
}
//添加任务函数
template<class T>
bool pthread_pool::Append(T* request)
{
//添加任务前需要给任务队列加锁,防止有线程对任务队列进行操作从而导致预期外的错误
lock.Lock();
//任务队列里面已经满了
if(q.size()>m_max_queue)
{
lock.Unlock();
return false;
}
q.push_back(requset);
lock.Unlock();
//通过条件变量唤醒一个线程来进行处理任务
sem.Post();
return true;
}
//工作函数
template<class>
void pthread_pool<T>::worker(void *arg)
{
pthread_pool *pool=(pthread_pool *)arg;
pool->run();
}
//运行线程池
template<class>
void pthread_pool<T>::run()
{
while(!stop)
{
//先调用信号量的Wait,如果信号量为0表示队列里面没有任务,一直阻塞等到信号量值+1
sem.Wait();
//给任务队列上锁
lock.Lock();
T* request=q.front();
q.pop();
lock.Unlock();
}
request->process();//对任务的处理,这就要看传递的任务中的处理函数了,这里就不多说了
}
线程池大整个构建大概就是这样了,可能有一点不足,还请各位大佬多多指教