谈异步线程池的设计
谈异步线程池的设计
异步线程池的作用就是统一进行线程调度,利用有限的线程处理系统中所有的异步任务。其实一个系统中的异步线程数量跟
CPU的核数是有一定的关系,线程太多,容易浪费资源,而且各个线程之间的CPU调度,也不见得使性能提高,反而会对性能有一定的影响。
但是,线程多,不容易照成死锁。为什么呢?
很多情况,我们在设计异步线程池的时候,很少去考虑异步任务的依赖的关系,假如一个异步任务在执行过程中会再创建一个异步任务,
并且等待这个异步任务的返回,才最终返回。因此异步任务之间也存在依赖关系。理想的情况下,异步任务之间如果不存在依赖关系,那么
阻塞是作用在调用者线程,这样是不会照成死锁的。因为即使线程数量有限,总会把任务执行完毕。但是如果异步任务如下图的依赖关系,
在有限的线程池规律条件下,是会死锁的。
图一:异步任务的依赖关系
从图一可以看出,假如线程池规模是100,那么如果有100一个并发产生的异步任务A、B、C任务,那么A、B、C产生的
子任务D、E、F这些子任务是无法得到线程池去执行的。
那么如何解决呢?解决死锁的方法,就是破坏产生死锁的一个条件。
按照理想的设计,如果异步任务队列执行的异步任务不存在同步等待,那么不会出现这样的问题,因为每个任务执行了,
如果还未执行完,可以被调度到等待队列,或者只是执行一些无法等待的操作。但是这种设计在除了操作系统外,
好像很难实现,因为不管无论怎么设计,总会在某一个线程去等待,取决业务,有时可以执行某个动作后,
把该任务转移到其他线程池,比如操作系统线程池。例如文件异步读写任务,就是把任务提交到操作系统线程去执行,
用户线程池不会堵塞。
好像谈多了一点,还是聚集一下在有依赖、等待问题的异步任务设计问题的解决上,前面提到解决这样的问题就是破坏死锁的条件,
那么如何破坏呢?
解决方法如下:
1)比较粗鲁的方法,各个异步任务都有自己的线程池,但是这样做存在线程利用率低的问题。
2)根据异步任务的依赖顺序,来确定任务的优先级,比如最底层依赖是优先级最高的,从依赖层来设计线程池,属于同层的任务有共同的线程池来完成。
同层的异步任务没有依赖关系,其产生的依赖任务由其他线程池完成,因此不会产生死锁,只是处理时间的问题。
不过方法二相对方法一没有什么改进之处,只是线程利用率在某种情况下相对高点。
3)在统一的线程池内处理,只是对不同优先级的任务做流控。比如在任何时刻不允许某几个优先级的异步任务耗尽线程池中的线程,总得预留一些线程处理其他异步任务, 在下发异步任务的地方进行阻塞。
方法三,可以编写相对通用的异步线程池。
4)如果有可能,尽量采用不阻塞的任务,比如采用利用操作系统的异步机制,将任务下发到操作系统的队列,等待其回调。比如文件系统的AIO、epoll等。
或者从业务角度划分业务线程,比如IO线程、SOCKET线程、数据库线程这些解耦合的异步线程。异步线程处理的任务之间是不依赖的,
把阻塞作用在使用者身上。