如何确保N个线程以大致相同的速度运行?

时间:2009-03-13 09:15:53

标签: java multithreading join erlang simulation

我正在研究编写一个物理模拟软件的想法,其中每个物理元素都将在自己的线程中进行模拟。

这种方法有几个优点。它在概念上非常接近现实世界的运作方式。将系统扩展到多台机器要容易得多。

但是,为了实现这一点,我需要确保所有线程以相同的速度运行,并对“相同”进行相当自由的解释。比如说在彼此的1%之内。

这就是为什么我不一定需要Thread.join()之类的解决方案。我不想要一些超级控制的学校情妇,确保所有线程定期与彼此同步。我只需要能够询问运行时(无论是哪种 - 可能是Java,Erlang,或者最适合此问题的任何东西)以大致相同的速度运行线程。

任何建议都会非常感激。

更新2009-03-16

我要感谢所有回答这个问题的人,特别是那些回答基本上都是“不要这样做”的人。由于每个人的评论,我现在更了解我的问题,而且我不太确定我应该像我原先计划的那样继续。尽管如此,我觉得彼得的答案是问题本身的最佳答案,这也是我接受它的原因。

14 个答案:

答案 0 :(得分:13)

如果没有协调,你就无法做到这一点。如果一个元素最终需要比另一个元素更便宜的计算(以一种潜在的非显而易见的方式)会怎样?

你不一定需要超级控制器 - 你可以在每个线程中保留一些步数计数器,并有一个全局计数器指示“最慢”的线程。 (当每个线程完成一些工作时,它必须检查它是否落后于其他线程,如果是这样则更新计数器。)如果一个线程注意到它比最慢的线程要远很多,它可能只是等待一下(可能在监视器上。)

只是经常这样做,以避免由于共享数据争用而产生太多开销,我认为它可以很好地工作。

答案 1 :(得分:12)

您需要某种同步。 CyclicBarrier课程符合您的要求:

  

允许a的同步辅助   一组线程都等待每一个   另一个达到共同的障碍点。   CyclicBarriers在程序中很有用   涉及固定规模的党   必须偶尔等待的线程   对于彼此。屏障被称为   循环因为它可以在之后重复使用   等待线程被释放。

每次'tick'之后,你可以让所有的线程等待其他线程,这些线程速度较慢。当剩余的线程到达屏障时,它们都将继续。

答案 2 :(得分:6)

线程意味着完全独立地运行,这意味着以任何方式同步它们总是很痛苦。在你的情况下,你需要一个中央“时钟”,因为没有办法告诉VM每个线程应该获得相同数量的......呃...它应该得到什么?相同数量的RAM?可能没关系。相同数量的CPU?您的所有对象是否都非常相似,每个对象都需要相同数量的汇编指令?

所以我的建议是使用一个中央时钟,向每个进程广播时钟滴答。每个进程中的所有线程都读取滴答(应该是绝对的),计算它们看到的最后一个滴答的差异,然后相应地更新它们的内部模型。

当一个线程完成更新时,它必须让自己进入睡眠状态;等待下一个打勾。在Java中,在“tick received”锁上使用wait()并使用“notifyAll()”唤醒所有线程。

答案 3 :(得分:4)

我建议不要在任何可能的情况下使用线程,因为如果你不小心它们只会在以后添加问题。在进行物理模拟时,您可以使用数十万个离散对象进行更大规模的模拟。你不可能在我所知道的任何操作系统上创建这么多线程,即使你可能会像狗屎那样表现!

在你的情况下,你可以创建许多线程,并在每个线程中放置一个事件循环。 “主”线程可以对执行进行排序并将“进程”事件发布到每个工作线程以唤醒它并使其完成一些工作。这样,线程就会一直睡到你告诉它们工作。

你应该能够让主线程以允许所有工作线程在下一个滴答之前完成的速率打勾。

我不认为线程是您问题的答案,除了并行化到少量工作线程(等于机器中的核心数量),每个线程对一系列物理对象进行线性排序。你仍然可以通过这种方式使用主/事件驱动的方法,但是你会消除很多开销。

答案 4 :(得分:3)

请不要。线程是O / S抽象,允许出现并行执行。使用多个CPU和多核CPU,O / S可以(但不必)在不同的核心之间分配线程。

我认为最接近您的可扩展性愿景的是使用工作线程,其大小与您拥有的核心数量大致匹配,并在它们之间分配工作。粗略草案:定义一个ActionTick类,它对一个粒子进行更新,让工作线程从共享队列中选择ActionTicks进行处理。有了这样的解决方案,我看到了几个挑战。

  1. 线程开销:您在不同的工作线程之间获得上下文切换开销。线程本身很昂贵(如果实际上并不像进程那么毁灭):使用不同的线程池大小测试性能。在核心数量之外添加更多线程往往会降低性能!
  2. 同步成本:您会获得多个争用点:访问工作队列中的一个,但更糟糕的是,访问模拟世界。您需要划分每个ActionTick的效果或实现大量锁定/解锁。
  3. 优化物理的难度。你想界定每个ActionTick看到的对象/粒子的数量(距离截止?模拟空间的3D树细分?)。根据模拟域,您可以通过检查项目子集中是否需要进行任何更改来消除大量工作。在排队工作项之前进行这些类型的优化更容易,而不是作为分布式算法。但是,那部分模拟成为潜在的可扩展性瓶颈。
  4. 复杂性。线程和并发性为解决方案引入了几种蠕虫。始终首先考虑其他选项 - 但如果您需要它们,请在创建自己的工作项调度,锁定和执行策略之前尝试线程......
  5. 警告:我没有使用任何大型模拟软件,只是一些业余爱好者代码。

答案 5 :(得分:3)

正如你所提到的,有很多“不要做这个”的答案。大多数人似乎将线程读作Java使用的OS线程。既然你在帖子中提到了Erlang,我想发布一个更多以Erlang为中心的答案。

使用进程(或者actor,微线程,绿色线程,有时也称为)来模拟这种模拟并不一定需要任何同步。实质上,我们有几个(很可能是数千或数十万)物理对象需要模拟。我们希望尽可能逼真地模拟这些对象,但也可能涉及某种实时方面(不一定是这样,你在问题中没有提到这一点)。

一个简单的解决方案是为每个对象生成一个Erlang进程,向所有对象发送tick,并在继续下一个tick之前收集模拟结果。这实际上是同步一切。它当然更像是一种确定性解决方案,并不保证任何实时属性。这些过程如何相互通信以获得计算所需的数据也是非常重要的。您可能需要以巧妙的方式对它们进行分组(碰撞组等),对睡眠对象进行休眠过程(Erlang对它有足够的支持)以加快速度。

要获得实时属性,您可能需要限制进程执行的计算(交易速度的准确性)。这可能是通过发送没有等待答案的刻度并让对象处理回复每个刻度及其当前位置和您需要的其他数据来完成的(即使它可能只是近似于时间)。正如DJClayworth所述,这可能会导致模拟中出现错误。

我想从某种意义上讲,问题实际上是关于是否有可能利用并发的优势来获得某种优势。如果您需要同步,则表明您不需要在每个物理对象之间进行并发,这是一个非常强烈的信号。因为你通过等待其他进程实际上浪费了大量的计算时间。您可以在计算过程中使用并发,但我认为这是另一种讨论。

注意:这些想法都不考虑实际的物理计算。这不是Erlang强大的一面,也许可以在C库中执行,也可以根据您想要的特性类型执行。

注意:我不知道有没有这样做过(尤其不是我),所以我不能保证这是合理的建议。

答案 6 :(得分:2)

即使使用完美的软件,硬件也会阻止您这样做。硬件线程通常没有良好的性能。如果线程在+ -10%的性能范围内运行,那么在很短的时间内你会很幸运。

当然,这些都是异常值。有些芯片组会在省电模式下运行某些内核,而其他芯片组则不会。我相信其中一台Blue Gene研究机器有软件控制的硬件线程调度而不是锁。

答案 7 :(得分:2)

默认情况下,Erlang会尝试在可用线程上均匀分布其进程。默认情况下,它还会尝试在所有可用处理器上运行线程。因此,如果您有足够的可运行Erlang进程,那么您将获得相对均衡的平衡。

答案 8 :(得分:1)

我不是一个线程专家,但它们不是彼此独立的线程的全部要点 - 并且是非确定性的?

答案 9 :(得分:1)

我认为你在问题中有一个根本的误解:

  

概念上非常接近真实世界的工作方式

现实世界根本不像线程那样工作。大多数机器中的线程不是独立的,实际上甚至不是同时的(OS将使用上下文切换)。当有很多IO或等待发生时,它们提供最大的价值。

最重要的是,当更复杂的事情发生时,现实世界并没有“消耗更多的资源”。想想从高处落下的两个物体之间的差异,一个物体顺利下落,另一个物体表现出某种复杂的翻滚动作......

答案 10 :(得分:1)

我会制作一种“时钟发生器” - 并在那里注册每个新的对象/线程。当delta-t通过时,时钟将通知所有已注册的对象。 但是,这并不意味着每个对象都需要一个单独的线程。理想情况下,您将拥有与处理器一样多的线程。 从设计的角度来看,您可以通过Executor或线程池将对象任务的执行分开,例如,当一个对象收到tick事件时,它会进入一个线程池并安排自己执行。

答案 11 :(得分:0)

要实现这一目标,必须要做两件事。您必须确保每个CPU核心具有相同数量的线程,并且您需要某种同步。

同步可以相当简单,比如在执行计算时为每个线程检查“循环完成”变量,但是你无法避免它。

答案 12 :(得分:0)

在电机控制下工作我已经使用了一些数学运算来保持速度处于稳定状态。 该系统具有PID控制,比例,积分和微分。但这是模拟/数字系统。也许可以类似地使用来确定每个线程必须运行的时间,但我能给你的最大提示是所有线程都将具有时钟同步。

答案 13 :(得分:0)

我首先承认我不是线程专家,但这听起来像是一种非常错误的方法来进行模拟。正如其他人已经评论过太多的线程在计算上是昂贵的。此外,如果您计划做我认为您正在考虑做的事情,那么您的模拟可能会产生随机结果(如果您正在制作游戏,则无关紧要)。

我会使用一些工作线程来计算模拟的离散步骤。