我该如何写任务? (并行代码)

时间:2010-05-11 22:19:58

标签: parallel-processing tbb

我对intel线程构建块印象深刻。我喜欢我应该如何编写任务而不是线程代码,我喜欢它如何在我的有限理解下工作(任务在池中,在4个核心上不会有100个线程,任务不能保证运行,因为它不是它自己的线程可能会进入池中。但它可能与另一个相关的任务一起运行,所以你不能做像典型的线程不安全的代码那样的坏事。)

我想了解更多关于编写任务的信息。我喜欢这里的“基于任务的多线程 - 如何编程100个核心”视频http://www.gdcvault.com/sponsor.php?sponsor_id=1(目前第二个最后一个链接。警告它不是'很棒')。我最喜欢的部分是“解决迷宫最好并行完成”,这大约是48分钟(你可以点击左侧的链接。如果有的话,这部分真的是你需要看的。)

但是,我希望看到更多代码示例和一些如何编写任务的API。有没有人有很好的资源?我不知道一个或多个代码如何将它推到一个池上,或者当你需要复制所有内容以及将所有内容推送到池中时,它们看起来有多奇怪。

2 个答案:

答案 0 :(得分:7)

Java有一个类似于Thread Building Blocks的并行任务框架 - 它被称为Fork-Join框架。它是available,可与当前的Java SE 6一起使用,并包含在即将发布的Java SE 7中。

除了javadoc类文档之外,还有可用于开始使用框架的资源。从jsr166 page开始,提到了这一点 “还有一个wiki包含这些类的附加文档,注释,建议,示例等。”

fork-join examples,例如矩阵乘法是一个很好的起点。

我使用fork-join框架来解决一些Intel's 2009 threading challenges。该框架重量轻,开销低 - 我是Kight's Tour问题的唯一Java条目,并且在竞争中表现优于其他条目。可以从挑战站点下载java源和写入文件以供下载。

编辑:

  

我不知道课程是怎样的   代码可能会把它推到上面   游泳池[...]

您可以通过继承ForKJoinTask个子类之一来创建自己的任务,例如RecursiveTask。以下是如何并行计算斐波纳契数列。 (取自RecursiveTask javadocs - 评论是我的。)

 // declare a new task, that itself spawns subtasks. 
 // The task returns an Integer result.
 class Fibonacci extends RecursiveTask<Integer> {
   final int n;      // the n'th number in the fibonacci sequence to compute
   Fibonnaci(int n) { this.n = n; } // constructor
   Integer compute() {   // this method is the main work of the task
     if (n <= 1)         // 1 or 0, base case to end recursion
        return n;
     Fibonacci f1 = new Fibonacci(n - 1);  // create a new task to compute n-1
     f1.fork();                            // schedule to run asynchronously
     Fibonacci f2 = new Fibonacci(n - 2);  // create a new task to compute n-2
     return f2.invoke() + f1.join();       // wait for both tasks to compute.
       // f2 is run as part of this task, f1 runs asynchronously. 
       // (you could create two separate tasks and wait for them both, but running
       // f2 as part of this task is a little more efficient.
   }
 }

然后运行此任务并获得结果

// default parallelism is number of cores
ForkJoinPool pool = new ForkJoinPool(); 
Fibonacci f = new Fibonacci(100);
int result = pool.invoke(f);

这是让事情变得简单的一个简单例子。在实践中,性能不会那么好,因为与任务框架的开销相比,任务执行的工作是微不足道的。根据经验,任务应该执行一些重要的计算 - 足以使框架开销无关紧要,但并不是在运行一个大任务的问题结束时最终得到一个核心。将大型任务拆分为较小的任务可确保一个核心在其他核心处于空闲状态时不会做大量工作 - 使用较小的任务可以保持更多核心繁忙,但不能太小以至于任务无法正常工作。

  

[...]或奇怪的代码看起来如何   你需要复制一切   以及推动了一切   到了游泳池。

只有任务本身才会被推入池中。理想情况下,您不希望复制任何内容:为了避免干扰和锁定的需要,这会降低程序的速度,理想情况下您的任务应该使用独立的数据。只读数据可以在所有任务之间共享,不需要复制。如果线程需要合作构建一些大型数据结构,那么最好分别构建它们,然后在最后组合它们。组合可以作为单独的任务完成,或者每个任务可以将其中的一部分添加到整体解决方案中。这通常需要某种形式的锁定,但如果任务的工作量远远大于更新解决方案的工作,那么这不是一个相当大的性能问题。我的Knight's Tour解决方案采用这种方法来更新电路板上的常见旅游资料库。

使用任务和并发是常规单线程编程的范例转换。通常有几种设计可以解决给定的问题,但其中只有一些适用于线程解决方案。可能需要一些尝试来了解如何以多线程方式重新熟悉问题。学习的最佳方法是查看示例,然后亲自尝试。总是分析,并考虑改变线程数量的影响。您可以在池构造函数中显式设置池中使用的线程数(核心数)。当任务线性分解时,随着线程数量的增加,您可以预期接近线性加速。

答案 1 :(得分:0)

使用声称解决无法解决的“框架”(最佳任务调度是NP难的)根本不会对你有所帮助 - 阅读书籍而不是关于并发算法的文章。所谓的“任务”只不过是用于定义问题可分离性的奇特名称(可以彼此独立计算的部分)。 可分离问题的类别非常小 - 旧书已经涵盖了这些问题。

对于不可分离的问题,您必须规划各阶段之间的阶段和数据障碍以交换数据。同步数据交换的数据障碍的最佳协调不仅仅是NP难,而且无法在原则上的一般方法 - 您需要检查所有可能的交错的历史 - 这就像已经指数集的幂集(如数学中从N到R)。我提到的原因是要明确表示没有任何软件可以为您做到这一点,并且如何做到这一点本质上取决于实际算法以及并行化是否可行(即使理论上可行)。

当你进入高并行性时你甚至无法维护一个队列,你甚至不再拥有一个内存总线 - 想象一下100个CPU尝试同步一个共享int或尝试做内存总线套接。您必须预先计划并预先配置将要运行的所有内容,并在白板上证明其正确性。英特尔的线程构建模块在这个世界上是一个小孩子。它们适用于仍可共享内存总线的少量内核。在没有任何“框架”的情况下,运行可分离的问题是很明智的。

因此,您必须尽可能多地阅读不同的并行算法。为一个问题研究近似最优数据屏障布局通常需要1 - 3年。当你在单个芯片上说16+核时,它就变成了布局,因为只有第一个邻居可以有效地交换数据(在一个数据屏障周期内)。因此,通过使用IBM的实验性30核CPU而不是英特尔的销售宣传或一些Java玩具来查看CUDA以及论文和结果,您将学到更多东西。

要注意浪费的资源大小(核心数和内存数)远远大于它们实现的加速的演示问题。如果需要4个内核和4个RAM才能解决2倍速的问题,那么该解决方案无法实现并行化的扩展。