龙空技术网

线程操纵术之更优雅的并行策略

阿里云开发者 484

前言:

现时我们对“recursive什么意思”大概比较着重,咱们都需要分析一些“recursive什么意思”的相关内容。那么小编在网摘上网罗了一些对于“recursive什么意思””的相关知识,希望咱们能喜欢,小伙伴们快快来了解一下吧!

点击链接阅读原文,获取更多技术内容:线程操纵术之更优雅的并行策略-阿里云开发者社区

本文详细介绍了并行编程以及一些并行问题案例中的真实业务场景。如何写出更优雅的并行程序?有哪些风险和注意事项?本文来为你解答。

作者 | 隰宗正(霜键)

来源 | 阿里云开发者公众号

Photo by Tomas Sobek on Unsplash

引子0.1 从一道小学奥数题说起……

星期天小明和妈妈两个人要做好多家务。他们的任务总共分为两部分,分别是卫生间和厨房的工作。

在卫生间的工作包括:擦镜子要7分钟,手洗衣服要30分钟,晾衣服要5分钟,刷厕所要10分钟。在厨房的工作包括:洗菜要5分钟,做饭要20分钟,倒垃圾要5分钟。

其中晾衣服必须在手洗衣服完成后才能做,做饭必须在洗菜完成后才能做。并且同一项工作最多只能一个人操作。

问题:小明和妈妈干完所有这些事情最少用多长时间?

0.2 问题解析

首先我们画出两部分的工作示意图:

答案比较显而易见,我们只要努力让两个人做家务的时间尽量相等,那么总体时间就是最少。简单排列组合后,不难得出答案为42min(任务的最小排列组合方式不止1种)

0.3 进一步思考

得出答案非常容易,在这个问题上,对我们在并行编程上有什么启示呢?

首先看一组错误的任务调度策略:

这一组策略第二个人先把小任务几乎做完,然后才开始大任务,导致第一个人完成任务后,长达12分钟没有其他事情做了。而在有任务时长的先验知识后,我们采取类似贪婪的思想,两个人在任一空闲时刻,永远做占用时间最大的任务,这样就把更多小任务留到了最后,总体可以节省更多时间。

带着这个思考,开始尝试书写更优雅的并行编程策略……

一、并行编程

并行编程是一种利用多个处理器或计算资源同时执行多个任务的编程方式,以提高计算效率和性能。它涉及任务划分、同步与互斥、通信与通信开销等概念和技术。并行编程的正确实现需要解决数据竞争、负载平衡、同步与互斥等挑战,并借助调试和性能分析工具来提高开发效率和程序性能。

1.1 并发与并行

并发是指多个任务交替执行的能力,而并行是指多个任务同时执行的能力。并发可以在单个处理器上通过快速切换任务的上下文来实现,而并行需要多个处理器或计算资源的支持。

并行编程无疑是一个广阔的领域,旨在通过同时执行多个任务或操作来提高程序的性能和效率。与传统的串行编程相比,它允许多个任务在同一时间段内并行执行,从而加快计算速度和提高系统的吞吐量。

并行编程广泛应用于需要处理大量数据或执行复杂计算的领域,如科学计算、数据分析、图像处理和机器学习等。它可以利用多核处理器、分布式系统或GPU等硬件资源,通过将问题划分为多个子任务,使用多个执行单元同时处理任务,从而实现性能的提升。

1.2 Java与并行编程

有许多编程语言提供了良好的支持和工具来完成并行编程,而Java 是开发并行应用程序的理想语言之一。它的原生线程库java.lang.Thread 允许任务并行、异步运行,可以总体上提高应用程序的执行速度,并允许在更短的时间内完成更复杂的任务。此外,Java 还提供了广泛的库和框架,可用于快速高效地开发应用程序,Java自身的语言特性也使开发人员能够创建安全可靠的跨平台应用程序。

1.2.1 Java线程与任务

Java线程与任务是Java编程语言中用于并发编程的重要组件。它们允许开发人员在程序中同时执行多个任务,提高程序的并发性和性能。

1.线程(Thread):线程是操作系统能够进行运算调度的最小单位。在Java中,线程是通过Thread类来表示的。每个线程都有自己的执行路径,并且可以独立执行,有自己的程序计数器、栈和本地存储等。通过创建线程对象并调用其start()方法,可以启动一个新的线程。Java中的线程有两种方式创建:继承Thread类和实现Runnable接口。

2.任务(Task):任务是需要在线程中执行的具体工作单元。在Java中,任务通常是通过实现Runnable接口或者Callable接口来定义的。Runnable接口定义了一个run()方法,该方法中包含了任务的具体逻辑。Callable接口类似于Runnable接口,但它可以返回执行结果,并且可以抛出异常。任务通常由线程池来管理和调度。

3.线程池(ThreadPool):线程池是一种管理和复用线程的机制,它可以提高线程的利用率。Java中的线程池是通过ThreadPoolExecutor类来实现的。线程池维护一个线程的集合,可以根据需要创建新的线程,也可以复用空闲的线程。通过将任务提交给线程池,线程池会自动调度线程来执行任务。

1.2.2 使用 Java 库进行并行编程

1.并行流(Parallel Stream):Java 8引入了并行流的概念,它是一种针对集合数据的高级抽象,可以在多个线程上并行地执行流操作。通过将集合转换为并行流,可以自动将任务分割为多个子任务,并在多个处理器上并行处理。并行流使用Fork/Join框架来实现任务的划分和合并。

2.任务执行器(Executor):是一个接口,位于java.util.concurrent包下,它的作用主要是为我们提供任务与执行机制(包括线程使用和调度细节)之间的解耦。执行器对于管理线程和确保线程安全非常有用。

3.Fork/Join框架:它是Java提供的一个用于并行编程的框架,它基于“分而治之”(divide and conquer)的思想,用于实现任务的划分和合并。Fork/Join框架在Java 7中被引入,通过提供一种简单且高效的方式来利用多核处理器和多线程执行任务。

1.3 “分而治之”的哲学

在使用 Java 进行并行编程中,本文重点介绍Fork/Join框架,该框架也是作为Java 并行流 API的实现。

1.3.1 Fork-Join 框架

在实际应用场景中,我们可能有多个任务,也可能有一个非常艰巨的任务,而这个大任务可以拆分为较小的子任务,并且,这些子任务可能会被拆分为更小的任务。在这些情况下,Fork/Join 池会派上用场。概括来说,Fork/Join 框架通过使用分而治之和工作窃取机制来实现更高程度的并行性。

fork/join 池以递归方式划分较大的任务,直到可以按顺序计算子任务而不会进一步分解。在任务完成执行后,它们返回结果,这些结果被连接回来,然后返回最终结果。下图是并行计算0~100000的fork-join示意图。

在使用fork-join框架中,需要注意:

1.Fork/Join 本身不能分割任务,也不能自行合并结果。需要我们指定一种划分任务和合并结果的方法。

2.Fork/Join 框架无法决定一项任务是否可以进一步划分。我们必须指定一种识别它的方法,例如设置一个特定的阈值,当达到该阈值时,将按顺序计算任务。

1.3.2 使用 Fork/Join 框架实现并行算法1.3.2.1 创建ForkJoinTask任务

在实践中,使用ForkJoin框架,必须首先创建一个ForkJoinTask任务。它提供在任务中执行fork()join()操作的机制。通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:

RecursiveAction用于没有返回结果的任务RecursiveTask用于有返回结果的任务。

创建任务唯一需要做的是重写compute()方法并实现逻辑。该方法规定了程序ForkComputationJoin 操作的具体位置。

内容剩余60%,完整内容可点击下方链接查看:线程操纵术之更优雅的并行策略-阿里云开发者社区

阿里云开发者社区,千万开发者的选择。百万精品技术内容、千节免费系统课程、丰富的体验场景、活跃的社群活动、行业专家分享交流,尽在:阿里云开发者社区-云计算社区-阿里云

标签: #recursive什么意思 #多线程策略