这是使用Scala 2.8 Actors。我有一个可以并行化的长期工作。它包括大约65万单位的工作。我将它划分为2600个不同的单独子任务,并为每个子任务创建一个新的演员:
actor {
val range = (0L to total by limit)
val latch = new CountDownLatch(range.length)
range.foreach { offset =>
actor {
doExpensiveStuff(offset,limit)
latch.countDown
}
}
latch.await
}
这种方法效果相当不错,但总体上需要2 + h才能完成。问题在于,与此同时,我创建的任何其他演员都做正常的任务似乎被最初的2600名演员挨饿,这些演员也耐心等待他们的时间在一个线程上运行,但是等待的时间比任何新的演员要长来吧。
我怎样才能避免这种饥饿?
初步想法:
更新
有些人质疑Actors的使用,特别是因为消息传递能力没有在工作者中使用。我假设Actor是一个非常轻量级的抽象,围绕一个ThreadPool处于或接近相同的性能级别,只需手动编写基于ThreadPool的执行。所以我写了一个基准:
import testing._
import java.util.concurrent._
import actors.Futures._
val count = 100000
val poolSize = 4
val numRuns = 100
val ActorTest = new Benchmark {
def run = {
(1 to count).map(i => future {
i * i
}).foreach(_())
}
}
val ThreadPoolTest = new Benchmark {
def run = {
val queue = new LinkedBlockingQueue[Runnable]
val pool = new ThreadPoolExecutor(
poolSize, poolSize, 1, TimeUnit.SECONDS, queue)
val latch = new CountDownLatch(count)
(1 to count).map(i => pool.execute(new Runnable {
override def run = {
i * i
latch.countDown
}
}))
latch.await
}
}
List(ActorTest,ThreadPoolTest).map { b =>
b.runBenchmark(numRuns).sum.toDouble / numRuns
}
// List[Double] = List(545.45, 44.35)
我在ActorTest中使用了Future抽象,以避免将消息传递回另一个actor以表明工作已完成。我惊讶地发现我的Actor代码慢了10倍。请注意,我还创建了我的ThreadPoolExecutor,其初始池大小用于创建默认的Actor池。
回过头来看,似乎我可能过度使用了Actor抽象。我将考虑使用单独的ThreadPools来完成这些不同的,昂贵的,长期运行的任务。答案 0 :(得分:6)
无论您有多少演员,如果您没有明确配置您的日程安排,所有演员都会使用单分叉/加入调度程序(针对容量为4的线程池运行,如果我没错的话)。这就是饥饿的来源。
从评论到默认Actor实施:
可以配置运行时系统 使用更大的线程池大小(for 例如,通过设置
actors.corePoolSize
JVM属性)。scheduler
的{{1}}方法 trait可以被覆盖以返回aActor
,哪个 调整其线程池的大小以避免 演员造成的饥饿 调用任意阻塞方法。该ResizableThreadPoolScheduler
JVM属性 可以设置为actors.enableForkJoin
,在这种情况下为a 使用false
默认情况下执行actor。
另外:scala-lang有趣thread on schedulers。
答案 1 :(得分:4)
从你的例子看来,你根本不需要使用演员,因为你没有将消息传递给你的工作单位,或者回复,甚至是循环。
为什么不创建一个Future
的负载然后等待它们完成?这样,底层的Fork Join Pool可以完全自由地决定系统的适当并行级别(即线程数):
import actors.Futures._
def mkFuture(i : Int) = future {
doExpensiveStuff(i, limit)
}
val fs = (1 to range by limit).map(mkFuture)
awaitAll(timeout, fs) //wait on the work all finishing
请注意,如果昂贵的工作不受CPU限制(可能是IO绑定),那么通过并行处理更多任务而不是系统具有内核,您只能从并行中获得受益。
答案 2 :(得分:3)
我没有使用具有该语法的actor,但默认情况下我认为scala中的所有actor都使用线程池。