基础概念
- 概念
一种线程使用模式,线程过多会带来调度开销,影响缓存局部性和整体性能,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,避免了在处理短时间任务时创建和销毁线程的代价,他能保证对内核的充分利用,还能防止过分调度,也就是用来解决效率问题。 - 应用场景
1、需要大量的线程来完成任务,且完成任务的时间比较短。
2、对性能要求苛刻的应用
3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用 - 实现实例
例如在双十一期间,会有很多人在淘宝上买东西,向淘宝发出购物请求,淘宝服务器就要对这些请求进行处理,服务器上肯定是多执行流并发/并行处理,如果请求来了就创建执行流进行处理,会存在两个问题:
(1)若峰值压力下,会创建线程过多,有可能导致资源耗尽;
(2)任务处理过程中,线程的创建和销毁成本过高;
因此在服务器启动的时候就创建大量的线程并且创建一个线程安全的任务队列作为线程池,任务一旦来了,就将任务抛入线程池的任务队列中,线程池的一个线程会循环去队列中获取任务并且处理; - 实现
(1)通过任务类,实现向线程池抛入任务时,既抛入数据也抛入处理方式的思路;
(2)线程池中的线程都只需要获取任务对象后,调用Run接口就可以实现任务处理;
(3)线程池只需要向外提供一个任务入队接口即可,任务的处理都是在线程池内部完成的;
实现代码:
(1)定义一个Task类,也就是任务,实现两数相加功能;
#include<iostream>
#include <queue>
using namespace std;
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
class Task
{
private:
int x;
int y;
public:
Task()
{}
~Task()
{}
void SetData(int _x,int _y)
{
x=_x;
y=_y;
}
int Run()
{
return x+y;
}
};
(2)然后在线程池类中实现功能,定义一个任务队列用来进行任务数据的拿和放(类似于生产者-消费者模型),定义线程的个数,要实现互斥,需要有互斥量,要实现同步,需要有条件变量;
class ThreadPool
{
private:
int num;//表示有多少线程
queue<Task> q;//任务队列
pthread_mutex_t lock;
pthread_cond_t cond;
(3)这些是要实现类内功能的接口函数
private:
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnlockQueue()
{
pthread_mutex_unlock(&lock);
}
bool QueueIsEmpty()
{
return (q.size()==0 ? true:false);
}
void ThreadWait()
{
pthread_cond_wait(&cond,&lock);
}
void PopTask(Task& t)
{
t=q.front();
q.pop();
}
void PushTask(Task& t)
{
q.push(t);
}
void NotifyThread()
{
pthread_cond_signal(&cond);
}
(4)然后进行构造和析构的定义,主要是对信号量和锁进行初始化;
public:
ThreadPool(int _num=6)
:num(_num)
{
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&cond,NULL);
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
(5)创建num个线程,对线程进行初始化
void InitThreadPool()
{
int i=0;
pthread_t tid;
for(;i<num;++i)
{
pthread_create(&tid,NULL,HandlerTask,(void*)this);//创建num个线程
}
}
(6)这个是线程池的重中之重,在创建线程中,线程处理函数由于在类内,实现多线程,由于pthread_create函数的第三个参数线程处理函数,但是对于类内的非静态函数,已经隐含地传入了this指针,就不满足线程处理函数的回调机制,因此需要将线程处理函数设置为static的,因为static类型的成员函数没有this指针,所以可以在pthread_create中将线程处理函数的参数传成this指针,用来调用成员函数;由于我们不想让主线程阻塞等待,因此可以将当前线程分离,由于要实现互斥,先将任务队列加锁,如果队列为空,此时线程要等待任务的到来,直到队列不为空,拿任务(就相当于生产消费),然后解锁,之后对拿到的任务进行处理,任务的处理不需要在锁里面,因为发现当得到了分配的线程就没有人与你竞争了,自然不需要锁的保护,然后打印处理后的结果即可。
private:
static void* HandlerTask(void* arg)//成员函数,在类内使用多线程需要将线程处理函数设置为static的
{
pthread_detach(pthread_self());//将当前线程分离
ThreadPool *tp=(ThreadPool*)arg;
for(;;)
{
tp->LockQueue();
if(tp->QueueIsEmpty())//如果队列为空,线程等待有任务到来
{
tp->ThreadWait();
}
Task t;
tp->PopTask(t);//如果任务不为空,拿任务
tp->UnlockQueue();
//任务拿出来后开始处理任务
int result=t.Run();
cout<<"Thread :"<<pthread_self()<<",result :"<<result<<endl;
}
}
静态函数无法直接访问线程池对象,因此将这个对象的this指针通过线程入口函数传入,在线程内部就可以访问到这个对象了,但是类外无法使用;
(7)最后实现添加线程功能,也就是对任务队列加锁,放任务,然后解锁,通知线程处理任务即可。
void AddTask(Task& t)
{
LockQueue();
PushTask(t);//将任务t放进来
UnlockQueue();
NotifyThread();//通知线程
}
};
(8)进行验证即可
int main()
{
ThreadPool *tp=new ThreadPool(5);
tp->InitThreadPool();
int count=0;
for(;;)
{
int x=count%1000+100;
int y=count%2000+300;
count++;
Task t;
t.SetData(x,y);
tp->AddTask(t);
sleep(1);
}
delete tp;
}
这个代码的运行结果就是:
[Daisy@localhost test_2019_11_8_1]$ ./thread_pool
Thread :139724298610432,result :400
Thread :139724298610432,result :402
Thread :139724290217728,result :404
Thread :139724281825024,result :406
Thread :139724273432320,result :408
Thread :139724265039616,result :410
Thread :139724298610432,result :412
Thread :139724290217728,result :414
Thread :139724281825024,result :416
Thread :139724273432320,result :418
Thread :139724265039616,result :420
Thread :139724298610432,result :422
Thread :139724290217728,result :424
Thread :139724281825024,result :426
Thread :139724273432320,result :428
Thread :139724265039616,result :430
==总结:==线程池是为了大量的线程来完成任务,且完成任务的时间比较短的情况而产生的,节省了开辟销毁线程的资源开销。
线程池适用于大量数据请求的多任务并发处理场景;
线程池源代码(github):
https://github.com/wangbiy/Linux3/commit/46eb8e10e8bf20a91d462dbf02ed29814a7d1a6d