2

我正在研究一个简单的进化算法。

这个怎么运作:

我有一张网格图,上面的每个单元格要么是空白的,要么是植物的,要么是草食动物的。植物和食草动物是生物(植物不是,无论如何),所以它们都有能量和颜色。生物每隔几毫秒(当计时器触发时)做一些事情。植物不断补充能量,食草动物失去能量。如果植物完全充电,它会朝随机方向(北、南、东或西)看,如果该方向上的相邻单元格是空白的,则植物会在该单元格中繁殖。草食动物通过吃植物来恢复能量。当轮到草食动物做事时,它会随机查看相邻的单元格。如果它是空的,食草动物就会移动到那里。如果它是一种植物,食草动物会尝试吃掉它然后移动到那里。草食动物的颜色越接近植物的颜色,草食动物成功的机会就越大。如果草食动物的能量达到 0,它就会死亡。如果草食动物达到最大能量,它也会繁殖。后代的颜色总是与父母的颜色略有不同。

这是一张图片:

在此处输入图像描述

这是从内部吃掉的很多植物。

一段时间后,系统正常化:

在此处输入图像描述

大块消失,随时出现几种颜色。当然,这是意料之中的。

所以,我现在有一个工作程序。但是,它是由一个线程运行的,我想让它成为多线程的。我打算为地图上的每个单元格制作一个线程。

我知道这是矫枉过正,但我​​还是想这样做。那样的话,我可以拥有这些小原子碎片,细胞,它们都可以异步工作,它们可以在运行中相互插入,就像拼图一样。

这就是基本的想法。我想让细胞尽可能地自主。我试图实现这一点:我为每个单元创建了一个摇摆计时器,当计时器触发时,我启动了一个线程让单元运行。我还为每个单元添加了锁,这样当多个邻居尝试访问同一个小区。这样做的问题是每次单元尝试做某事时都会调用一个新线程。我知道这非常消耗资源(第一手经验),所以我想以某种方式为每个单元创建持久线程。每个线程将处理它自己的单元格的计时器、动作侦听器并在计时器触发时运行其单元格的动作。这些线程基本上永远不会停止。我需要帮助,因为我不确定如何实现这样的系统。

编辑:澄清问题:我需要一个线程示例,它侦听计时器事件并在计时器触发时运行东西。这一切都应该发生在线程内部。

4

4 回答 4

2

我认为增加Threads 的数量只有在增加 cpu 核心时才有意义。您可以考虑超线程。因此,例如,如果您有一个启用了 HT 的四核 cpu,那么它可以同时处理 8Thread秒。

另一件事是,这通常以其他方式完成。假设您有 10 个对象(植物、食草动物等)。每个都有一个方法,例如calculateNextState(). 您只需在循环中计算 10 个对象的所有状态,然后当您为所有对象创建新状态时刷新 GUI。你做这个广告无限。您不需要计时器,您只需考虑计算所花费的时间。

一个例子:

你有一个食草动物,它以 10 个单位的速度(在 x 轴上)移动。它的位置是(2,4)上一次计算之间经过的时间,now()200ms你将它的位置更新为(4,4)。等等...

至于并行化:您可以创建 aThreadPool并将Runnables 推入其中。每个Runnable实际上都是计算 a 的下一个状态的函数GameObject(我只是为你拥有的生物命名)。当所有ThreadPools 完成他们的工作后,您可以更新 GUI。

主要的是:如果您为计算设置了固定时间,则无法真正更新 gui 上任意数量的对象,因为它可能需要比您设置的时间更长的时间

示例:如果您说“我希望每秒更新 1 次”,但您有 5.000.000 个对象,则计算可能需要 2 秒。这就是为什么我建议你应该使用我描述的模型。所有状态计算都应该是可并行的,以便您可以在线程之间分配工作。

但是有一个警告:您必须考虑到 2 种不同的植物/食草动物试图扩展到同一个单元格中的情况,该单元格在之前的计算之后是空的。

于 2013-10-12T16:23:20.200 回答
1

还不如写我自己的答案。

首先:您将无法使用纯线程每个实体、基于计时器的方法准确地表示您的模拟世界

原因如下:

世界模型与 CPU 模型

当然,左侧是世界模型,右侧是实现的简单线程模型,基于您的方法。

现在,要指出的重要一点是:

  • 在您的仿真模型中:
    1. 每个实体的状态都是完全内化的。草食动物、植物等包含其自身运作所需的所有信息。
    2. 所有实体都是自治的。一个实体不关心其他东西是否在移动、被吃掉等,或者有多少其他实体同时在做。
    3. 所有实体都是实时运作的。他们如何根据他们的内在状态出现,就是他们如何出现在这个世界上。
  • 在实际的实现模型中:
    1. 主存储器中有一个全局状态。所有实体都需要将线程本地状态与全局状态同步,否则会遇到不一致。
      • 实体线程争夺有限的 CPU 资源。线程可以随时进入非活动状态 - 甚至“你的”线程都不能启动!有多少其他线程正在执行变得非常重要。
      • 实体线程竞争主存访问。当一个线程锁定一个公共锁时,另一个线程必须等到它被解锁。
    2. 实体线程的运行与它们的显示方式完全分离(即在 GUI 查看器中)。

仅从这三点来看,我希望可以清楚地看出这些模型是不兼容的。

您可能会尝试将您的世界模型适合线程模型,但随后您会遇到改变您的世界以适应实现的问题。例如,您提到的锁-它们会根据您的方法改变您的世界的运作方式。

当然,如果在这种特定情况下您对此感到满意,请继续(这意味着您也可以接受通常伴随着大量线程的速度下降,以及随之而来的所有好问题同步)。

但是,在一般情况下,您应该根据您的规范选择您的实现,而不是相反。

如果您注意上述内容,您有很多选择,其中大部分依赖于Adam Arold描述并由 clwhisk 改进的离散模拟步骤。这些模型包括:

  • 有限数量的工作线程使用ThreadPoolExecutor或处理工作单元ForkJoinPool
  • 将网格模型编码为一组方程,
  • 等等等等
于 2013-10-12T18:44:16.977 回答
0

正如亚当的回答所暗示的,这通常是通过时间步长完成的。每个生物对其下一个动作都有一个倒计时,最简单的倒计时是一个递减的整数,calculateNextState()每一步都会调用所有内容。但是,您的灵活性远不止于此。例如,将生物存储在按(双)倒计时排序的优先级队列中。您可以通过打乱他们在队列中的顺序来随机打破平局,或者在倒计时中添加一个随机元素。最重要的是,您可以使用常规方法完全控制模拟。线程不是这项工作的工具——它们听起来像是在做额外的工作,应该在每次有并行任务时使用,但事实并非如此。

于 2013-10-12T17:52:17.153 回答
0

您没有定义持久线程对您意味着什么。

线程,如进程在当前机器和操作系统上不是持久的(阅读应用程序检查点)。例如,它们在 Java 的意义上是不可序列化的。

您需要实现自己的计算持久性实现(而不是线程)。以延续传递风格设计您的程序可能会有所帮助。拥有自己的持久计算概念(例如,通过让每个线程运行一个小循环,其状态是持久的和可序列化的)。

不要想着拥有数千个(甚至数百个)线程;它们是昂贵的资源。考虑有一个小的线程池(最多几十个线程)。线程池的大小应该是可配置的,并且不应该取决于数据的大小(或您正在计算的问题)。

您可能需要考虑期货和承诺,例如Java 期货(在小型线程池中共享线程)。

于 2013-10-12T16:19:05.577 回答