前言
工作中经常会用到各种池,因此写几篇随笔学习并记录一下,最后附有实现代码。另见:连接池学习、对象池学习
概念
为了避免每次执行任务都新建线程、销毁线程的开销
设计
-
线程队列
- 固定数量
- 初始化时,创建指定个数的线程,每个线程会循环从任务队列中取任务并执行,或者等待任务队列有任务添加进来
-
任务队列
- 使用普通队列
- 锁保护入队、出队操作
- 使用无锁队列
- 队列的最大长度,以及达到最大长度之后的行为
- 待补充
- 指示队列中有任务的条件变量
- 使用普通队列
-
添加任务
- 判断是否达到最大任务数量
- 通知条件变量
-
执行任务
- 循环从任务队列中取任务并执行
- 等待条件变量
-
线程池状态
- 提供基本的获取线程池状态的接口
注意事项
添加任务的性能
当执行任务非常高效时,性能瓶颈会发生在向队列中添加任务阶段
有关条件变量的使用
- 执行notify时不需要获取锁
- 修改“条件”一定要锁保护
- 生产者(执行notify操作的线程)修改“条件”时需要加锁
- 消费者(执行wait操作的线程)在 检测条件 到 执行wait()函数之间 也要加锁(即保护消费者在 检测条件 到 调用wait()操作 的期间内,生产者是没有机会修改“条件”并调用notify()的,从而保证消费者不会错过通知)
因此当对修改“条件”操作加锁保护后的两种情况:
- 消费者先判断条件不满足并进入等待状态与生产者后修改条件并通知条件变量是同步发生的:消费者每次检测条件前处于获取锁状态,如果检测到条件不满足,就会执行wait()函数,当wait中的一些操作完成(如把消费者线程添加到等待队列)之后释放锁,之后生产者才可以获取锁并修改条件,然后(可以先释放锁)才调用notify()函数,消费者因此不会错过通知
- 生产者先修改条件(为满足),消费者不会检测条件为(不满足)并阻塞,生产者的nofity不起作用
当“条件”为原子变量时
对”条件“的读取和修改不需要再加锁保护
- 执行wait()函数之前仍然有可能发生其他线程完成条件设置并通知条件变量
- 解决:由于检测条件与执行wait()函数之间是获取锁状态,那么在调用notify()之前也获取锁就可以保证消费者不错过通知(生产者阻塞直到获取锁后才能调用notify())
线程池中的线程函数是循环的从任务队列中取任务并执行,当取不出任务时就等待在条件变量上,“条件”就是“任务队列是否为空”,如果任务队列使用的是无锁队列的话,没有必要对进队、出队操作进行加锁,检测队列是否为空时(出队成功)也没必要去加锁;当省略了在生产者线程中的加锁的操作时,确实会有可能导致消费者错过通知,但实际情况下某个线程错过某一次通知,其他线程可能会继续从任务队列中取出了这个任务,如果所有线程都错过了某一次通知,通常情况下线程池是不断接收任务的,不至于每次的通知都会错过
cpp thread pool 项目
https://github.com/chenguang9239/CTPL
参考
https://stackoverflow.com/questions/17101922/do-i-have-to-acquire-lock-before-calling-condition-variable-notify-one
条件变量 之 稀里糊涂的锁
https://en.cppreference.com/w/cpp/thread/condition_variable/notify_one