Scala:对象初始值设定项中的并行集合导致程序挂起 [英] Scala: Parallel collection in object initializer causes a program to hang

查看:44
本文介绍了Scala:对象初始值设定项中的并行集合导致程序挂起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've just noticed a disturbing behavior. Let's say I have a standalone program consisting of a sole object:

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

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

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

该程序是完全无害的,当 for 循环中使用的范围不是并行范围时,可以正常执行,输出如下:

The program is perfectly innocent and, when the range used in for loop is not a parallel one, executes properly, with the following output:

内循环:1
做某事
内循环:2
做某事

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

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

Unfortunately, when using the parallel collection, the program just hangs without ever invoking the doSomething method, so the output is as follows:

内循环:2
内部循环:1

Inside loop: 2
Inside loop: 1

然后程序挂了.
这只是一个讨厌的错误吗?我使用的是 Scala-2.10.

And then the program hangs.
Is this just a nasty bug? I'm using scala-2.10.

推荐答案

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

This is an inherent problem which can happen in Scala when releasing a reference to the singleton object before the construction is complete. It happens due to a different thread trying to access the object ParCollectionInInitializerTest before it has been fully constructed. It has nothing to do with the main method, rather, it has to do with initializing the object that contains the main method -- try running this in the REPL, typing in the expression ParCollectionInInitializerTest and you'll get the same results. It also doesn't have anything to do with fork-join worker threads being daemon threads.

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

Singleton objects are initialized lazily. Every singleton object can be initialized only once. That means that the first thread that accesses the object (in your case, the main thread) must grab a lock of the object, and then initialize it. Every other thread that comes subsequently must wait for the main thread to initialize the object and eventually release the lock. This is the way singleton objects are implemented in Scala.

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

In your case the parallel collection worker thread tries accessing the singleton object to invoke doSomething, but cannot do so until the main thread completes initializing the object -- so it waits. On the other hand, the main thread waits in the constructor until the parallel operation completes, which is conditional upon all the worker threads completing -- the main thread holds the initialization lock for the singleton all the time. Hence, a deadlock occurs.

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

You can cause this behaviour with futures from 2.10, or with mere threads, as shown below:

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 中,然后写入:

Paste this into the REPL, and then write:

scala> ParCollection

然后 REPL 挂起.

and the REPL hangs.

这篇关于Scala:对象初始值设定项中的并行集合导致程序挂起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆