1. 什么是线程池?
在多线程程序中,如果频繁创建和销毁线程,成本会非常高。预先创建部分线程,这些线程的集合叫做线程池。这样,程序讲任务传递给线程池,线程池就启动一个线程来处理任务,任务结束之后,线程置于空闲状态,等待下一个任务,这样可以很好的提高性能。
和普通的多线程相比主要有两点不同:
- 任务由线程池管理,线程池拿到任务之后,会安排空闲的线程进行处理,如果没有则等待。
- 线程池可以同时处理多个任务,一个线程同时只能执行一个任务
我们可以把线程理解成一个散工,一般情况下,比如我们家里需要粉刷墙,则到劳工市场找一个散工来做这件事,雇主和散工直接交互。而线程池可以理解成散工的“工会”,我们打电话给工头,他来分配工人即可,他掌握着“散工”资源,雇主和散工之间加了一个“工头”。
2. 线程池有什么用?
减少频繁创建和销毁线程所损耗的性能,降低成本。
预先分配一定的资源,减少创建和销毁的代价,类似的技术还有对象池、连接池等其他池化技术。
3. 如何使用线程池?
线程池是一项成熟的技术,像boost、poco等框架中都有支持。使用简单,如下poco使用线程池的简单例子。
ThreadPool(int minCapacity = 2,
int maxCapacity = 16,
int idleTime = 60,
int stackSize = POCO_THREAD_STACK_SIZE);
/// Creates a thread pool with minCapacity threads.
/// If required, up to maxCapacity threads are created
/// a NoThreadAvailableException exception is thrown.
/// If a thread is running idle for more than idleTime seconds,
/// and more than minCapacity threads are running, the thread
/// is killed. Threads are created with given stack size.
创建线程池时,一般会指定线程池最小线程数和最大线程数,空闲时间,线程栈大小等。
ThreadPool tp(8, 128, 120);
TaskManager* tm= new TaskManager(tp); //初始化任务管理器与线程池进行关联
tm->start(new Task());//开启一个线程
tm->cancelAll();
tm->joinAll();
或者
ThreadPool tp(8, 128, 120);
RunnableAdapter<ThreadPoolTest> ra(*this, &ThreadPoolTest::hello);
tp.start(ra);
如果要自己实现一个线程池,要了解以下几点
重点数据结构是什么?
首先线程池要有默认线程数量,程序启动时,默认开启固定数量的线程。
再者,线程池不可能无限制的增长,要有个最大线程数量。
如果线程池空闲时间比较长,也应有一个空闲时间和最小线程数量。
还有如果任务多了,线程不够,需要增加x个;如果任务少了,线程要销毁x个,那么这个增减的步长也是线程池的属性之一。
管理者线程
工作线程的创建和销毁,管理线程的数量
任务队列
为了保证线程安全,必然包含 条件变量、互斥锁
如果任务队列中有数据(任务)的话,激活堵塞在条件变量上的线程
如果任务队列满了怎么办?
- 阻塞生产者往队列里扔数据
如果任务队列空了怎么办?
- 阻塞消费者(线程池)从队列里取数据
struct threadpool_t { // 保持线程安全 pthread_mutex_t lock; /* 结构体锁 */ pthread_mutex_t thread_counter; /* busy_thr_num锁 */ pthread_cond_t queue_not_full; /* 当任务队列满时,添加任务的线程阻塞,等待此条件变量 */ pthread_cond_t queue_not_empty; /* 任务队列里不为空时,通知等待任务的线程 */
pthread_t *threads; /* 线程数组 */ pthread_t adjust_tid; /* 管理线程*/ threadpool_task_t *task_queue; /* 任务队列 */
//数量 int min_thr_num; /* 最小线程数 */ int max_thr_num; /* 最大线程数 */ int live_thr_num; /* 存活线程个数 */ int busy_thr_num; /* 忙状态线程个数 */ int wait_exit_thr_num; /* 要销毁的线程个数 */
//队列信息 int queue_front; /* task_queue队头下标 */ int queue_rear; /* task_queue队尾下标 */ int queue_size; /* task_queue队中实际任务数 */ int queue_max_size; /* task_queue队列可容纳任务数上限 */
int shutdown; /* 标志位,线程池使用状态,true或false */ }; |
3.2 重要函数(实现)是什么?
- 创建线程池
- 分配线程池结构体空间,初始化参数
- 初始化任务队列
- 创建默认N个线程
- 工作线程处理
- 线程处理函数死循环,循环内访问线程池,前后加解锁
- 死循环,等待任务队列不为空的条件变量触发
- 若触发则进行工作
3.管理线程
- 死循环,去存活数量和忙碌数量
- 线程不够用的时候,创建线程
- 线程空闲的时候,销毁线程