随着近年来多核 CPU 的出现,并行编程是充分利用新处理工作资源的方式。并行编程是指由于多个处理核心的可用性,进程的并发执行。从本质上讲,与线性单核执行甚至多线程相比,这会极大地提高程序的性能和效率。Fork/Join 框架是 Java 并发 API 的一部分,该框架使程序员能够并行化算法。本文借助 Java 中可用的 Fork/Join 框架探索并行编程的概念。

概述

并行编程具有更广泛的内涵,无疑是一个广阔的领域,可以用几行来阐述。问题的症结很简单,但在操作上却很难实现。简单来说,并行编程意味着编写使用多个处理器来完成任务的程序,仅此而已!它几乎与多线程的想法押韵。但是,请注意它们之间有一些重要的区别。从表面上看,他们是一样的,但实质却是完全不同的。事实上,引入多线程是为了提供一种并行处理的错觉,根本没有真正的并行执行。多线程的真正作用是它窃取了 CPU 空闲时间并利用它来发挥自己的优势。

简而言之,多线程是任务的离散逻辑单元的集合,这些任务运行以获取它们的 CPU 时间份额,而另一个线程可能会暂时等待,例如,一些用户输入。空闲 CPU 时间最佳地在竞争线程之间共享。如果只有一个 CPU,它是时间共享的。如果有多个 CPU 内核,它们也都是时间共享的。因此,一个最优的多线程程序通过巧妙的分时机制来挤压 CPU 的性能。本质上,总是一个线程使用一个 CPU,而另一个线程正在等待。这以一种微妙的方式发生,用户会感觉到并行处理,实际上,处理实际上是快速连续发生的。多线程的最大优势在于它是一种最大限度地利用处理资源的技术。现在,这个想法非常有用,可以在任何一组环境中使用,无论是单个 CPU 还是多个 CPU。这个想法是一样的。

另一方面,并行编程意味着程序员可以并行使用多个专用 CPU。这种类型的编程针对多核 CPU 环境进行了优化。今天的大多数机器都使用多核 CPU。因此,并行编程在当今非常重要。即使是最便宜的机器也安装了多核 CPU。看看手持设备;即使它们是多核的。尽管多核 CPU 的一切看起来都很笨拙,但这也是故事的另一面。更多的 CPU 内核是否意味着更快或更高效的计算?实际上,在日常计算中,即使是单个 CPU 也很难保持忙碌。但是,多核在特殊情况下也有其用途,例如在服务器、游戏等或解决大问题时。拥有多个 CPU 的问题在于,它需要的内存必须与速度与处理能力相匹配,以及闪电般快速的数据通道和其他附件。简而言之,日常计算中的多个 CPU 内核提供的性能改进不能超过使用它所需的资源量。因此,我们得到了一台未被充分利用的昂贵机器,可能只是为了展示。

并行编程

与多线程不同,其中每个任务都是较大任务的离散逻辑单元,并行编程任务是独立的,它们的执行顺序无关紧要。任务是根据它们执行的功能或处理中使用的数据来定义的;这分别称为功能并行或数据并行。在功能并行中,每个处理器处理其部分问题,而在数据并行中,处理器处理其部分数据。并行编程适用于不适合单个 CPU 架构的较大问题库,或者可能是问题太大以至于无法在合理的估计时间内解决。因此,任务在处理器之间分配时,可以相对较快地获得结果。

Fork/Join 框架

Fork/Join 框架在 java.util.concurrent 包中定义。它包括几个支持并行编程的类和接口。它的主要作用是简化了多线程的创建过程、它们的使用,并使多处理器之间的进程分配机制自动化。使用该框架进行多线程和并行编程之间的显着区别与我们之前提到的非常相似。在这里,处理部分被优化为使用多个处理器,这与多线程不同,其中单个 CPU 的空闲时间在共享时间的基础上进行了优化。该框架的额外优势是在并行执行环境中使用多线程。

这个框架中有四个核心类:

1、ForkJoinTask<V>:这是一个定义任务的抽象类。通常,任务是在此类中定义的 fork() 方法的帮助下创建的。这个任务几乎类似于使用 Thread 类创建的普通线程,但比它轻。它应用的机制是它可以在加入 ForkJoinPool 的少量实际线程的帮助下管理大量任务。fork() 方法可以异步执行调用任务。join() 方法允许等待,直到调用它的任务最终终止。还有另一种方法,称为invoke(),它将fork 和join 操作组合成一个调用。

2、ForkJoinPool:这个类提供了一个公共池来管理 ForkJoinTask 任务的执行。它基本上为非 ForkJoinTask 客户端的提交以及管理和监控操作提供了入口点。

3、RecursiveAction:这也是 ForkJoinTask 类的抽象扩展。通常,我们扩展此类以创建不返回结果或具有 void 返回类型的任务。此类中定义的 compute() 方法被覆盖以包含任务的计算代码。

4、RecursiveTask:这是 ForkJoinTask 类的另一个抽象扩展。我们扩展这个类来创建一个返回结果的任务。而且,与 ResursiveAction 类似,它还包括一个受保护的抽象 compute() 方法。此方法被覆盖以包括任务的计算部分。

Fork/Join 框架策略

该框架采用递归分治策略来实现并行处理。它基本上将一个任务分成更小的子任务;然后,将每个子任务进一步划分为子子任务。这个过程递归地应用于每个任务,直到它小到可以按顺序处理。假设我们要增加 N 个数字的数组的值。这就是任务。现在,我们可以将数组一分为二,创建两个子任务。将它们中的每一个再次划分为另外两个子任务,依此类推。通过这种方式,我们可以递归地应用分而治之的策略,直到将任务挑出到一个单元问题中。然后,这个单元问题可以由可用的多核处理器并行执行。在非并行环境中,我们要做的就是循环遍历整个数组,依次进行处理。鉴于并行处理,这显然是一种低效的方法。但是,真正的问题是每一个问题都可以分而治之吗?绝对不!但是,有些问题通常涉及某种特别适合这种方法的数据数组、集合和分组。顺便说一句,有些问题可能不使用数据收集,但可以优化以使用并行编程策略。

结论

这是对并行编程及其在 Java 中的支持方式的简要描述。一个公认的事实是,拥有 N 个内核并不会让一切都快 N 倍。只有一部分 Java 应用程序有效地使用了这个特性。并行编程代码是一个困难的框架。此外,有效的并行程序必须考虑负载平衡、并行任务之间的通信等问题,有一些算法更适合并行执行,但很多不适合。无论如何,Java API并不缺乏它的支持,我们可以随时修改 API 以找出最适合的 API。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部