Scala:对象初始化程序中的并行收集导致程序挂起

标签 scala scala-collections

我刚刚注意到一个令人不安的行为。
假设我有一个由一个对象组成的独立程序:

object ParCollectionInInitializerTest {
  def doSomething { println("Doing something") }

  for (i <- (1 to 2).par) {
    println("Inside loop: " + i)
    doSomething
  }

  def main(args: Array[String]) {
  }
}

该程序是完全无辜的,当 for 循环中使用的范围是 时不是 一个并行的,正确执行,输出如下:

Inside loop: 1
Doing something
Inside loop: 2
Doing something



不幸的是,当使用并行集合时,程序只是挂起,没有调用 doSomething 方法,所以输出如下:

Inside loop: 2
Inside loop: 1



然后程序挂起。
这只是一个讨厌的错误吗?我正在使用 scala-2.10。

最佳答案

这是一个固有问题,在构造完成之前释放对单例对象的引用时,可能会在 Scala 中发生。这是由于另一个线程试图访问对象 ParCollectionInInitializerTest 而发生的。在它完全建成之前。
main 无关相反,它与初始化包含 main 的对象有关。方法 -- 尝试在 REPL 中运行它,输入表达式 ParCollectionInInitializerTest你会得到同样的结果。它也与作为守护线程的 fork-join 工作线程无关。

单例对象被延迟初始化。每个单例对象只能初始化一次。这意味着访问对象的第一个线程(在您的情况下是主线程)必须获取对象的锁,然后对其进行初始化。随后出现的每个其他线程都必须等待主线程初始化对象并最终释放锁。这是在 Scala 中实现单例对象的方式。

在您的情况下,并行收集工作线程尝试访问单例对象以调用 doSomething ,但在主线程完成初始化对象之前不能这样做——所以它会等待。另一方面,主线程在构造函数中等待直到并行操作完成,这是以所有工作线程完成为条件的——主线程一直持有单例的初始化锁。因此,出现死锁。

您可以使用 2.10 的 future 或仅使用线程导致此行为,如下所示:

def execute(body: =>Unit) {
  val t = new Thread() {
    override def run() {
      body
    }
  }

  t.start()
  t.join()
}

object ParCollection {

  def doSomething() { println("Doing something") }

  execute {
    doSomething()
  }

}

将其粘贴到 REPL 中,然后编写:
scala> ParCollection

并且 REPL 挂起。

关于Scala:对象初始化程序中的并行收集导致程序挂起,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15176199/

相关文章:

mysql - 使用 Slick (Scala) 将记录插入数据库,实体的最佳实践

scala - 最佳实践 : "If not immutable create copy"-pattern

用于分组的 Scala 集合,同时保持顺序

scala - 递归遍历 Map 中的值

scala - Scala集合中缺少par方法

scala - Akka context.parent 意外值

scala - sbt 在动态任务中设置 java 选项

Scala:表达式的类型与形式参数类型不兼容

方法参数中的 Scala 映射无法添加键值

Scala for 同时循环遍历两个列表