目录
一、线程池简介
线程池:由一堆工作线程+一个线程安全的任务队列构成。
外界将需要处理的任务,加入到线程安全的任务队列中,线程池中的工作线程不断的从任务队列中取出任务进行处理。
二、应用场景
应用场景:有大量数据请求,需要并发处理的场景。
1)需要大量的线程来完成任务,且完成任务的时间较短;
2)对性能要求苛刻的应用,比如要求服务器迅速响应客户请求;
3)接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
大量请求需要处理,若针对每一个请求创建一个线程,会存在以下问题:
1)资源耗尽,系统崩溃风险
2)在任务的并发处理中,执行流并不是越多越好
注:一个任务处理的总时间消耗 = 创建线程的时间 + 任务处理的时间 + 线程销毁的时间,则在任务处理中,大量的时间成本消耗在了线程的创建与销毁上。此时就可以选择应用线程池来进行任务处理。
三、线程池的实现
1.创建一堆线程
2.创建一个线程安全的任务队列
3.定义任务处理方法
不同的客户端可能有不同的请求,不同的请求右不同的处理方式。
解决方案:
让外界在传入请求的同时,附上此请求的对应处理方法,线程只需要根据请求和传入的处理方法去进行任务处理即可。
4.任务处理
四、代码实现
1.完整代码
#include<iostream>
#include<pthread.h>
#include<queue>
#include<cstdlib>
#include<unistd.h>
#define MAX_QUEUE 5
#define MAX_THREAD 5
//线程安全的任务队列
template <class T>
class BlockQueue{
private:
int _capacity;//缓冲区容量
std::queue<T> _queue;
pthread_mutex_t _mutex;
pthread_cond_t _cond_pro;
pthread_cond_t _cond_con;
public:
BlockQueue(int cap = MAX_QUEUE) : _capacity(cap) {
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_cond_pro, NULL);
pthread_cond_init(&_cond_con, NULL);
}
~BlockQueue() {
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond_pro);
pthread_cond_destroy(&_cond_con);
}
bool Push(const T &data) {
pthread_mutex_lock(&_mutex);
while (_queue.size() == _capacity) {
pthread_cond_wait(&_cond_pro, &_mutex);
}
_queue.push(data);
pthread_cond_signal(&_cond_con);
pthread_mutex_unlock(&_mutex);
}
bool Pop(T *data) {
pthread_mutex_lock(&_mutex);
while (_queue.empty()) {
pthread_cond_wait(&_cond_con, &_mutex);
}
*data = _queue.front();
_queue.pop();
pthread_cond_signal(&_cond_pro);
pthread_mutex_unlock(&_mutex);
}
};
//任务处理方法
typedef void (*handler_t)(int data);
class ThreadTask {
private:
int _data;
handler_t _handler;
public:
ThreadTask() {}
ThreadTask(int data, handler_t handler)
:_data(data)
,_handler(handler)
{}
void Run() {
_handler(_data);
}
};
//线程池
class ThreadPool {
private:
int thread_count;
BlockQueue<ThreadTask> _queue;
//这里需要设置为静态成员函数,否则参数多一个this指针,无法匹配
static void *Worker(void *arg) {
//不断取出任务并处理
ThreadPool *pool = (ThreadPool*)arg;
while (1) {
ThreadTask task;
pool->_queue.Pop(&task);
task.Run();
}
}
public:
ThreadPool(int tcount = MAX_THREAD, int qcount = MAX_QUEUE)
:thread_count(tcount)
,_queue(qcount)
{
int ret;
pthread_t tid;
//创建工作线程
for (int i = 0; i < tcount; ++i) {
ret = pthread_create(&tid, NULL, Worker, this);
if (ret != 0) {
printf("Create thread error!\n");
exit(0);
}
pthread_detach(tid);//将线程分离,不关心其退出
}
}
bool Push(const ThreadTask &task) {
_queue.Push(task);
}
};
//处理方法
void Conduct(int data) {
printf("Thread: %p --- sleep %d seconds!\n", pthread_self(), data);
sleep(data % 4 + 1);
}
int main() {
ThreadPool pool;
for (int i = 0; i < 20; ++i) {
ThreadTask task(i, Conduct);
pool.Push(task);
}
while (1) sleep(1);//防止程序退出
return 0;
}
2.实现效果
3.注意事项
1)线程入口函数类型不匹配:
若入口函数是一个类的成员函数,那么参数列表会有一个隐藏的this指针,导致入口函数类型不匹配,
解决方法:
可将线程的入口函数设置为静态成员函数(静态成员函数,不含this指针)。
2)静态成员函数无法直接访问类的普通成员变量:
静态成员函数在调用时,因为没有传入this指针,无法直接访问类的普通成员变量。
有问题的解决方案:
将入口函数中访问类的成员变量(阻塞队列),设置为静态。
存在问题:若将类的成员变量(阻塞队列)设置为静态,则类实例化的所有对象共用同一个静态成员变量(阻塞队列),所以不可取。
合适的解决方案:
在创建线程池时,将线程池对象的this指针传入到线程的入口函数中,通过this指针,来访问其对应的成员变量(阻塞队列)。