Scala并行集合和可变状态

时间:2011-05-30 09:37:54

标签: scala parallel-processing

我有一个函数,通过更新映射执行一步计算(底层是一个可变的HashMap)。我想并行执行其中几个计算(每个“链”在自己的可变HashMap上工作)。

我是通过将HashMaps放入并行集合并使用map将函数应用于每个HashMap来实现的。

现在我在地图中遇到了丢失的条目。调试时,一旦异常断点停止程序,映射就会包含该条目(然后通过丢弃某些堆栈帧层来重新启动计算)。

当我使用顺序集合时,此行为已消失。那么有可能存在一些不正常行为(或错误),这是由同一个HashMap在不同的线程中处理的吗?

我没有发布代码示例,因为我不认为该行为是可重现的。据我所知,唯一可变数据包含在那些保持计算状态的HashMaps中。

根据要求提供我的代码样本,其中创建了并行映射(reset)并修改了(step)。

class ParallelInferer[V <: DiscreteVariable, TInf <: InferenceAlgorithm[V]](val numChains: Int,
                                              val inferer: InferenceAlgorithm[V],
                                              val random: Random) {
  //tuples of inferer state and the according random number generator
  protected var states: ParSeq[(inferer.Repr,Random)] = _
  protected var steps = 0

  reset()

  def reset() {
    steps = 0

    val seed = random.nextInt()

    //todo why does parallelizing fail here (key not found on a map)
    states = (1 to numChains).par
      .map(n => new Random(seed + n))    //create the rngs
      .map(rng => (inferer.createInitialState(rng),rng))
  }

  def step() {
    //advance every chain by one
    states = states.map{case (repr, rng) => (inferer.computeStep(repr, rng),rng)}
    steps = steps + 1
  }
}

代码说明

ParallelInferer类旨在(也)用于不可变推理。因此,在发布的代码中不会直接看到可变性,但我认为它是显示的重要部分。

每个推理算法都有一个状态概念,这个状态属于InferenceAlgorithm#Repr类型 - 在使用inferer.Repr作为states变量的一部分时很明显。引用者通过将Repr(和随机对象)映射到具有Repr函数的新computeStep来工作。这可以在def step()中看到。现在,一些提交者使用可变HashMap作为其州的一部分。他们的computeStep方法在变异之后返回它作为参数获得的相同地图。

问题

  1. 我可以以某种方式修复此行为吗?
  2. 我是否滥用并行集合并且应该将我的任务并行化?
  3. 修改

    我刚刚再次运行并行化版本,我认为它也会导致算法无法终止,尽管它在顺序运行时会发生。好吧,不是令人惊讶,不是吗?

    有人可以猜测为什么会这样吗?

1 个答案:

答案 0 :(得分:5)

是的,绝对的。默认情况下,Mutable HashMaps不是线程安全的,以这种方式使用它们会导致未定义的行为。缺少条目实际上是一种相当温和的表现形式。根据底层实现,还可能将HashMap数据结构破坏到程序进入无限循环的程度。

有很多方法可以解决这个问题,这会有不同的编码复杂性和性能权衡。最简单的方法是使用同步的哈希映射而不是非同步的哈希映射。

import scala.collection.mutable._

val map = new HashMap[Key, Value] with SynchronizedMap[Key, Value] 

我不会说根本问题是您错误地使用并行集合,而是使用可变数据结构的任何并行程序将会出现这样的问题。更多Scala-ish方法是使用不可变映射,并让并行处理步骤返回新的不可变映射。由于不可变哈希映射的底层实现,这听起来很昂贵,但不一定如此。