ForkJoinPool分支/合并框架工程使用的工作窃取

ForkJoinPool分支/合并框架

在必要的情况下,讲一个大任务,进行拆分(fork)成若干个小任务(拆到不可拆为止),再将一个个小的任务运算的结果进行join汇总。

工作窃取的背景

分支/合并框架,里面提到了ForkJoinSumCalculator会将一个任务分成很多个子任务,一般来说分出大量的子任务是个好的选择。因为在理想的情况下,划分并行任务时,应该要让每个任务都用完全相同的时间完成,让所有的CPU内核都同样繁忙。但是实际中,每个子任务所花费的时间可能天差地别,要么因为划分策略低,要么因为不可预知的原因,比如磁盘访问慢,或者需要和外部服务协调执行。

工作窃取的使用

针对多个线程分配相同多子任务的时候,会出现不同线程完成所有任务的时间有快有慢的情况,分支/合并框架工程使用了工作窃取的技术来解决这个问题。在实际应用中,这些子任务被差不多的分配到ForkJoinSumCalculator中的所有线程上,每个线程都为分配给他的任务保存一个双向的链式队列,每完成一个任务,就会队列头上取出下一个任务开始执行。因为上面所述的原因,有些线程可能早早地完成了分配给他的任务,也就是他的队列已经空了,但其他的线程还是很忙。这个时候队列已经空了的线程并不会闲置下来,而是随机选择一个其他的线程从队列的尾巴上“偷走”一个任务。这个过程会一直继续下去,知道所有的任务都执行完毕,所有的队列都清空。这就是为什么要划成许多小任务而不是少数几个大任务的原因,他能有助于工作线程之间的平衡负载。

工作窃取算法的过程

当工作线程队列中有一个任务被分成两个子任务时,一个子任务就被闲置的工作线程给“偷走”了,这个过程可以不断的递归,直到规定子任务应顺序执行的条件为真。下图展示了工作窃取算法用于在池中的工作线程之间重新分配和平衡任务的过程。

总结

相比一般的线程池实现,ForkJoinPool分支/合并框架在对其中包含的任务的处理方式上具有优势,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在ForkJoinPool分支/合并框架上,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题(窃取过来)来执行,这种方式而减少了线程池的等待时间,提高了性能。

ps:写个博客记录一下自己学习的东西,如有错误请指出,万分感谢。

猜你喜欢

转载自www.cnblogs.com/skm-fightting/p/11224618.html