2010-04-10 62 views
7

我正在为我的Java并发课程开发一个教程。目标是使用线程池并行计算素数。将线程递归添加到Java线程池

该设计是基于Eratosthenes筛。它有一个n布尔数组,其中n是您检查的最大整数,数组中的每个元素表示一个整数。真是素数,假不是素数,数组最初都是真的。

线程池与固定数量的线程一起使用(我们应该尝试使用池中的线程数并观察性能)。

线程被赋予一个整数倍来处理。线程然后在数组中找到第一个不是线程整数倍数的真实元素。线程然后在线程池中创建一个新的线程,该线程被给予找到的号码。

新线程形成后,现有线程继续将其整数的整数倍数设置为false。

主程序线程以整数'2'开始第一个线程,然后等待所有产生的线程完成。然后分析出素数和计算所需的时间。

我遇到的问题是线程池中线程越多,线程速度越快,线程越慢。它应该越快越好!

所有关于Java线程池的东西都会在主线程中创建n个工作线程,然后等待所有线程完成。我使用的方法是递归的,因为工作者可以产生更多的工作线程。

我想知道发生了什么问题,如果Java线程池可以递归使用。

+3

继续使用线程方法,这是一种学习体验,当您完成后,您将对线程有所了解。谁关心Eratosthenes的筛子?许多专业程序员从不了解本页面的知识。只要记住,如果一个女人可以在9个月内有一个婴儿,并不意味着九个人可以做一个月! – 2010-06-24 23:51:30

回答

5

的线程将添加一些的下列问题你解决运行速度可能下降:

  • 线程创建开销:创建线程是昂贵的。

  • 处理器争用:如果有更多的线程比处理器执行它们,一些线程将暂停等待空闲处理器。结果是每个线程的平均处理速率下降。此外,操作系统还需要对线程进行时间片分割,这会浪费时间,否则将用于“真正的”工作。

  • 虚拟内存争用:每个线程都需要其堆栈的内存。如果你的机器没有工作负载足够的物理内存,每一个新的线程堆栈增加虚拟内存争导致寻呼其中会减慢速度

  • 高速缓存争:每个线程(大概)是扫描的不同部分该数组,导致内存缓存未命中。这会降低内存访问速度。锁定争用:如果您的线程全部读取和更新共享阵列并使用​​和一个锁定对象来控制对阵列的访问,则可能会遭受锁定争用。如果使用单个锁对象,则每个线程将花费大部分时间等待获取该锁。最终结果是计算被有效地串行化,并且整体处理速率下降到单个处理器/线程的速率。

前四个问题是固有的多线程,并没有真正的解决办法......除了没有创造太多的线程和重用您已经创建的。但是,有很多方法可以解决锁争用问题。例如,

  • 重新编码应用程序,以便每个线程都扫描多个整数,但是会扫描它自己的数组部分。这将消除阵列上的锁定争用,但是您需要一种方法来告诉每个线程要做什么,并且需要考虑到争用而设计。
  • 为阵列的不同区域创建一个锁定数组,并让线程根据他们正在操作的数组区域选择要使用的锁定。你仍然会争论,但平均而言,你应该得到较少的争论。
  • 设计并实现无锁解决方案。这将需要深入理解Java内存模型。要证明/证明无锁解决方案不包含微妙的并发缺陷将非常困难。

最后,递归创建线程可能是一个错误,因为这将使其难以实现线程重用和防抱死争的措施。

+0

简而言之,您必须测试添加线程是否可以提高性能,不要认为增加复杂性会提供更好的解决方案,因为很多时候它并不会。 – 2010-04-10 06:57:14

+0

@Peter - 我会说练习的要点是,您必须使用线程*正确的方式来获取应用程序的性能,以便在添加处理器时进行扩展。 – 2010-04-10 07:47:49

+0

我真的觉得只要给这篇文章一个投票是不够的,这是一个很好的总结多线程可能的缺点。 – posdef 2012-01-27 23:34:42

3

系统上有多少个处理器可用?如果#threads> #processors,添加更多的线程会降低计算限制任务的速度。

记住无论您开始多少个线程,它们仍然都共享相同的CPU。 CPU花在线程之间切换的时间越多,实际工作的时间就越少。

还要注意,与检查素数的开销相比,启动线程的开销很大 - 您可能需要数百或数千次乘法才能启动1个线程。

+0

我目前的计算机有两个内核,但代码最初是在具有4个内核(8个虚拟内存)的英特尔酷睿i7上进行测试的,它有类似的问题。即1个线程= 1s。 4个线程= 20s。 – ljbade 2010-04-10 03:51:56

0

线程池的关键点是让一组线程保持活动状态并重新使用它们来处理任务。通常情况下,模式是有一个任务队列,并从池中随机选择一个线程来处理它。如果没有空闲线程并且池已满,请等待。

你设计的问题不是线程池要解决的问题,因为你需要按顺序运行线程。如果我在这里错了,请纠正我。

线#1:设置2的倍数,以虚假

线#2:找到3,设置3的倍数,以虚假

线#3:找5,设置5的倍数,以虚假

线#4:发现7,设置7的多为假

....

这些线程需要以运行和他们交织(如何运行时间表ŧ下摆)事宜。

例如,如果线程#3在线程#1将“4”设置为false之前开始运行,它会找到“4”并继续重置4的倍数。这最终会做很多额外的工作,尽管最终结果是正确的。

+0

线程5永远不会“找到”4 - 它的唯一目的是删除5的所有倍数。一旦所有的工作线程完成删除非素数,主线程将“找到”剩余的数字。 – danben 2010-04-10 03:41:56

+0

如果线程#3需要等待线程完成之前启动的线程,那么我们不需要多个线程。 这里的问题是线程#3不需要等待线程#1去除* 2的所有倍数,但是它需要等待线程#1去除“足够的”2的倍数,然后才开始搜索。 – evergreen 2010-04-10 03:48:35

0

重组您的程序以提前创建一个固定的ThreadPoolExecutor。确保你调用ThreadPoolExecutor#prestartAllCoreThreads()。让您的主要方法提交整​​数2的任务。每个任务都将提交另一个任务。由于您使用的是线程池,因此您不会创建并终止一堆线程,而是允许相同的线程在新任务可用时执行新任务。这将减少整体执行开销。

您应该发现,在这种情况下,线程的最佳数量等于机器上的处理器数量(P)。通常情况下,线程的最佳数量是P + 1。这是因为P + 1使来自上下文切换的开销最小化,同时也使空闲/阻塞时间的损失最小化。