【问题标题】:Design considerations for Scala Worker ActorsScala Worker Actor 的设计注意事项
【发布时间】:2012-08-28 09:26:42
【问题描述】:

今年秋天我参加了分布式系统课程,由于 Scala 演员在这个领域非常有用,所以我刚刚开始学习 Scala。从到目前为止我从文本“Scala 中的演员”中读到的有关演员的内容中,我有一个问题似乎并没有在文本中非常清楚地解决。

我目前正在一台具有多核处理器的机器上对我的程序进行建模,并在 Master-Workers 模型中使用基于事件的参与者。现在,以下两种设计选择中的哪一种会导致所有处理器内核得到最佳使用?

1) 将 Actor 的数量限制在一个固定的较小数量,而不管主 Actor 有多少工作,分配给工作 Actor(顺便说一下,有时可能相当大)

2) 生成与 Master Actor 需要完成的作业数量一样多的 Actor

哪些因素会影响我对上述两种选择的决定?

更新:我打印出我正在运行的 100 个工作角色的线程 ID,这是输出 -

No. of Processors-2
MainActor is on thread:Thread[ForkJoinPool-1-worker-0,5,main]
AccumulatorActor is on thread:Thread[ForkJoinPool-1-worker-1,5,main]

Worker Actor-1 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-4 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-5 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-6 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-7 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-8 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-9 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-10 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-3 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-12 running on thread:Thread[ForkJoinPool-1-worker-0,5,main] 
Worker Actor-13 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-14 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-15 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-2 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-16 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-11 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-17 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-18 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-19 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-20 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-21 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-22 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-23 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-24 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-25 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-26 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-28 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-29 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-27 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-30 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-31 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-33 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-32 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-34 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-35 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-36 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-38 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-39 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-40 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-41 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-42 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-43 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-44 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-45 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-46 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-47 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-48 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-51 running on thread:Thread[ForkJoinPool-1-worker-1,5,main]
Worker Actor-50 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-54 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-49 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-56 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-57 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-58 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-55 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-59 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-52 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-61 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-60 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-63 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-53 running on thread:Thread[ForkJoinPool-1-worker-1,5,main]
Worker Actor-65 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-66 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-68 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-64 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-62 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-70 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-69 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-67 running on thread:Thread[ForkJoinPool-1-worker-1,5,main]
Worker Actor-72 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-73 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-71 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-75 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-78 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-79 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-80 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-81 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-82 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-83 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-74 running on thread:Thread[ForkJoinPool-1-worker-1,5,main]
Worker Actor-84 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-85 running on thread:Thread[ForkJoinPool-1-worker-1,5,main]
Worker Actor-77 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-76 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-86 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-87 running on thread:Thread[ForkJoinPool-1-worker-1,5,main]
Worker Actor-88 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-89 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-91 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-92 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-94 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-95 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]
Worker Actor-96 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-90 running on thread:Thread[ForkJoinPool-1-worker-1,5,main]
Worker Actor-98 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-99 running on thread:Thread[ForkJoinPool-1-worker-1,5,main]
Worker Actor-100 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-93 running on thread:Thread[ForkJoinPool-1-worker-2,5,main]
Worker Actor-37 running on thread:Thread[ForkJoinPool-1-worker-0,5,main]
Worker Actor-97 running on thread:Thread[ForkJoinPool-1-worker-3,5,main]

从上面可以看出,ForkJoinPool 中有四个工作线程在运行,而我的机器上有 2 个 cpu 内核。我不确定为什么会这样,因为快速查看 ResizableThreadPoolScheduler 的来源让我相信最初池中的工作线程数等于 cpu 内核数,并且这些是动态增加的,以防任何参与者在池中的一个工作线程上被阻塞。我使用的所有演员都是基于事件的(反应)并且不执行任何阻塞操作。一个理想的场景是在 ForkJoinPool 中只有 2 个线程,并让它们在执行每个参与者工作的两个核心上并行运行。我对此的理解是正确的还是我在这里遗漏了什么?

【问题讨论】:

  • 请记住,许多处理器都具有超线程,因此最佳线程数可能高于内核数。

标签: scala actor


【解决方案1】:

您听起来好像在考虑演员,就好像他们是线程一样。拥有演员系统的部分目的是将您遇到的问题与演员本身分离。演员的制作成本很低,所以有几千个演员就可以了。拥有数千名演员并不意味着他们每个人都会尝试同时运行。

Scheduler 管理如何执行参与者请求。默认情况下,您将获得 ForkJoinScheduler 或(在特殊 JVM 上) ResizableThreadPoolScheduler

在这两种情况下,并行任务的数量都由ThreadPoolConfig 控制,默认为max(2*cores,4)。在这个类中,你还可以看到如何通过系统属性覆盖这些默认值:

  • actors.enableForkJoin 允许您控制调度程序的选择
  • actors.corePoolSize 设置初始“线程”池大小
  • actors.maxPoolSize 设置最大尺寸

请注意,研究 Akka Actor 可能是一个更好的主意,因为它们可能会在某些时候使用 replace the original Scala actor implementation,提供更多功能并强制更清晰地遵守 Actor 模型(因为您只能获得 ActorRef 并且可以不再直接调用你的actor上的方法)。在 Akka 中,调度程序的等价物是 dispatchers

【讨论】:

  • 好吧,我采用了相同的方法。我意识到基于事件的参与者都共享来自公共线程池的线程,但是无论如何要知道参与者正在操作的线程的 id 吗?还有什么方法可以验证默认调度程序确实使用了所有可用的内核?
  • 您可以从您的 Actor 代码中调用 Thread.getCurrentThread。 ResizableThreadScheduler 的源代码(从上面的 Scaladoc 链接)也相当简短且易于理解。
  • 编辑了答案。很抱歉造成混乱。
猜你喜欢
  • 1970-01-01
  • 2012-05-16
  • 1970-01-01
  • 2022-11-30
  • 2011-06-06
  • 2016-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多