一、线程池简介
线程池:由一堆工作线程+一个线程安全的任务队列构成。
外界将需要处理的任务,加入到线程安全的任务队列中,线程池中的工作线程不断的从任务队列中取出任务进行处理。
二、应用场景
应用场景:有大量数据请求,需要并发处理的场景。
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指针,来访问其对应的成员变量(阻塞队列)。