18

不穏な行動に気づきました。唯一のオブジェクトで構成されるスタンドアロンプ​​ログラムがあるとしましょう。

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

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

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

プログラムは完全に無害であり、forループで使用される範囲が並列範囲でない場合、次の出力で適切に実行されます。

ループ内:1
何かをする
ル​​ープ内:2
何かをする

残念ながら、並列コレクションを使用すると、プログラムはdoSomethingメソッドを呼び出さずにハングするだけなので、出力は次のようになります。

内側のループ:2
内側のループ:1

そして、プログラムがハングします。
これはただの厄介なバグですか?私はscala-2.10を使用しています。

4

1 に答える 1

28

これは、構築が完了する前にシングルトンオブジェクトへの参照を解放するときにScalaで発生する可能性のある固有の問題です。ParCollectionInInitializerTestこれは、オブジェクトが完全に構築される前に、別のスレッドがオブジェクトにアクセスしようとしたために発生します。これはメソッドとは関係ありませんmain。むしろ、メソッドを含むオブジェクトを初期化することと関係があります。mainこれをREPLで実行し、式ParCollectionInInitializerTestを入力すると、同じ結果が得られます。また、デーモンスレッドであるフォークジョインワーカースレッドとは何の関係もありません。

シングルトンオブジェクトは遅延して初期化されます。すべてのシングルトンオブジェクトは一度だけ初期化できます。つまり、オブジェクトにアクセスする最初のスレッド(この場合はメインスレッド)は、オブジェクトのロックを取得してから初期化する必要があります。その後に来る他のすべてのスレッドは、メインスレッドがオブジェクトを初期化し、最終的にロックを解放するのを待つ必要があります。これは、シングルトンオブジェクトがScalaで実装される方法です。

あなたの場合、並列コレクションワーカースレッドはシングルトンオブジェクトにアクセスして呼び出すことを試みますdoSomethingが、メインスレッドがオブジェクトの初期化を完了するまでそうすることはできません-したがって、待機します。一方、メインスレッドは、すべてのワーカースレッドが完了することを条件として、並列操作が完了するまでコンストラクターで待機します。メインスレッドは、シングルトンの初期化ロックを常に保持します。したがって、デッドロックが発生します。

以下に示すように、2.10からの先物、または単なるスレッドでこの動作を引き起こす可能性があります。

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がハングします。

于 2013-03-02T15:59:57.503 に答える