• Java7中的ForkJoin并发框架初探(上)——需求背景和设计原理


    0. 处理器发展和需求背景

      回想一下并发开发的初衷,其实可以说是有两点,或者说可以从两个方面看。

    • 对于单核的处理器来说,在进行IO操作等比较费时的操作进行时,如果执行任务的方式是单任务的,那么CPU将会“空转”,直到IO操作结束。如果有多任务的调度机制,则在一个任务不需要CPU支持的时候,CPU可以被调度处理其他任务。简单地讲,并发可以提高CPU计算资源的利用率。

    • 对于多核,或者多个计算资源的情况下,并发可以在某种程度上达到“并行”,即同时运行,缩短了任务完成的时间,提高了任务完成的效率。

      我们再来看一下处理器计算能力的发展(讲并发或者并行基本都要提到),Intel的创始人之一Gordon Moore曾经说过一句话,大概意思是:当价格不变时,集成电路上可容纳的晶体管数目,约每隔18个月便会增加一倍,性能也将提升一倍。

      我们可以这样理解,处理器的计算能力在一定意义上和芯片上集成的晶体管数量有关,而这项集成技术的发展史飞快的。但是,什么事情都是有一个极限的,提升计算性能仅仅靠增加晶体管数量提高处理器主频是不现实的,于是多核处理器的概念就出来了。随着在硬件上多核处理器的发展和广泛使用,软件开发上的变革也在进行。简单来想,对于多个不相关的小任务来讲,可以分派到不同的处理器核心来进行处理。然而,对于一个比较大的任务,如何能够充分利用多核计算资源就是一个值得考虑的问题。解决这个问题的办法就是“分而治之”,而Fork Join正式这样一种思路的产物。

    1. Fork Join 的设计简介

      看过《Introduction to Algorithms》(《算法导论》)的朋友们应该还记得,在讲到归并排序(Merge Sort)和快速排序的时候,有一种很简单又很有效率的思路就是“分而治之”,即“分治法”。而Fork Join的思路也是同理,只不过划分之后的任务更适合分派给不同的计算资源,可以并行的完成任务。

    ForkJoin的任务分解和合并

      

    2. Fork Join 设计要点

      Fork Join设计出来就是为了提高任务完成的效率,围绕这个目标,有一些要点是设计中需要考虑的,下面就给出一些要点。

    • 线程的管理和线程的单纯性。基于如上的设计思路,我们可以看到子任务之间的相关性是相对比较简单的,可以并行处理。为了提高效率,并不需要重量级的线程结构和对应的线程维护,线程实现简单就好,满足需求即可,降低维护成本。

    • 队列机制,硬件支持一定是比较有限的,那么分解的任务应该用队列维护起来,一个好的队列设计是很有必要的。

    • “工作窃取”,也就是设计论文原文中提到的 Work Stealing 。对于负载比较轻的线程,可以帮助负载较重的执行线程分担任务。

      对于使用Fork Join的开发者来讲,需要注意:

    • 可用线程数和硬件支持。线程这东西,也是有开销的东西,绝对不是越多越好,尤其在硬件基础有限的情况下。

    • 任务分解的粒度。和前者有关系,就是分解的任务,“小”到什么程度是可以接受的,不可再分。

    3. Fork Join数据结构支持

      按照如上设计,分解执行一个大的任务,Fork Join至少需要考虑如下一些数据结构。

    • 轻量级的线程结构。

    • 维护线程的线程池,负责线程的创建,数量维护和任务管理。

    • 维护任务,并支持Work Stealing的双端队列。如下图。

    ForkJoin队列

      对于子任务的分解,可以从后端取出分解再放入,而对于WorkStealing则可以从头部取出,放入其他队列的尾部。到此,本文仅仅是对Fork Join的大致设计思路做一个描述、勾勒。下一篇文章中会对JDK1.7中给出的实现作出分析。

  • 相关阅读:
    WPF Expander 炫酷自定义Style
    C#8.0 中的 【索引与范围】
    Windows的图形设备接口与Windows绘图
    第一个Windows窗口应用程序
    0-1背包问题的分枝—限界算法
    哈密尔顿回路(旅行售货员问题)的回溯算法
    背包问题的贪心算法
    实现矩阵连乘的动态规划算法
    用分治策略实现棋盘覆盖问题
    sql注入实例分析
  • 原文地址:https://www.cnblogs.com/wxgblogs/p/5477059.html
Copyright © 2020-2023  润新知