同步任务
类架构种类
自顶向下依次为
1、线程池类ThreadPool
2、线程处理单元ProcessThread
3、任务类Task
4、线程安全队列ThreadSafeQueue
类架构细节
1、线程安全队列ThreadSafeQueue
本质上是一个队列,但是为了适应多线程,使得多个线程可以并发地操作该队列,包括读取、存入元素以及计算队列元素个数,需要加入线程保护——在操作该队列时需要加锁保护。具体体现在
(a)push和获取size时要加锁,可以用互斥锁
(b)pop操作时要保证队列中有元素,如果有元素,加互斥锁然后弹出元素,如果没有元素,需要等待,可以用条件变量来实现,启用wait()方法,一旦别的线程进行了push操作,通知本线程可以进行pop操作了
(c)为了保证潜在的某一线程的pop操作启用的条件变量能被唤醒,在push操作完成后还需启用notify()方法,通知正在pop等待的线程,可以继续它的操作了。
2、基本任务类Task
同步任务的任务类只是需要封装一个函数,该函数实现了具体的任务逻辑,该函数可以是任意函数,线程池中执行的具体函数可以是都不一样的,只需要保证每个函数都是用任务类包装起来。
3、线程处理单元ProcessThread
线程处理单元继承自threading库thread类,它本质上就是一个线程,而且需要强调的是它是针对单个线程进行编程实现,单个线程处理单元所需要完成是事情是不断循环地运行以下逻辑:
(a)从任务队列中取出一个元素——一个Task类的具体对象
(b)执行该对象中封装的具体函数逻辑
(c)跳回(a)重复执行
这里任务队列是一个线程安全队列ThreadSafeQueue,装载的元素是一个个相同或不同的Task类对象,由于是线程安全的,那么可以保证在第一步中如果任务队列不存在元素时不会直接报错,而是启用条件变量,等待任务队列中被放入一个元素。
具体是什么时候任务队列被放入一个新元素是不确定的,在程序中别的部分、别的时机有实现,要注意放入的时候要通知正在等待的线程可以继续它的操作了。
4、线程池类ThreadPool
线程池类里面需要定义两个类成员变量,一个是线程池pool,一个是任务队列task_queue,两者都是线程安全队列ThreadSafeQueue,但是装载的元素不同,任务队列在刚刚已经说过了,装载的是具体的Task对象,而线程池装载的是线程处理单元ProcessThread,一个线程处理单元就代表一条线程,分别从任务队列中取出元素去执行。
虽然线程池pool和任务队列task_queue都用了同种类型的容器ThreadSafeQueue去装载,但是线程池实际上只会在线程池类ThreadPool运行构造函数时,将所有的线程处理单元push进去,然后运行类的start()方法将所有线程启动,在程序执行完毕或者用户执行join方法时将所有的线程处理单元stop掉,然后一一pop()出去。
有一个疑问点:在程序运行时,并不会存在并发地去操作线程池中的元素,比如同时push多一个线程处理单元ProcessThread进线程池,或者pop走一个线程处理单元,存储这种元素的容器应该是不需要做线程保护的,这种操作在编程中似乎不太符合规范?但是用线程安全队列去装载这种单元总归是不会有错,只是还不知道必要性在哪里