Почему Scala зависает при оценке параметра по имени в Future?

Приведенный ниже (надуманный) код пытается напечатать параметр String по имени в будущем и вернуть его после завершения печати.

import scala.concurrent._
import concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

class PrintValueAndWait {
  def printIt(param: => String): Unit = {
    val printingComplete = future { 
      println(param);  // why does this hang?
    }
    Await.result(printingComplete, Duration.Inf)
  }
}

object Go {
  val str = "Rabbits"

  new PrintValueAndWait().printIt(str)
}

object RunMe extends App {
  Go
}

Однако при запуске RunMeпросто зависает при попытке оценить param, изменения printIt Принятие в параметре значения по значению приводит к возврату приложения, как и ожидалось. В качестве альтернативы, изменение printIt просто напечатать значение и вернуть синхронно (в том же потоке), кажется, тоже работает нормально.

Что именно здесь происходит? Это как-то связано с тем, что объект Go еще не полностью построен, и поэтому str поле еще не видно потоку, пытающемуся его распечатать? Здесь висит ожидаемое поведение?

Я тестировал Scala 2.10.3 как на Mac OS Mavericks, так и на Windows 7, на Java 1.7.

1 ответ

Решение

Ваш код блокируется при инициализации Go объект. Это известная проблема, см., Например, SI-7646 и этот вопрос SO

Объекты в scala лениво инициализируются, и в течение этого времени берется блокировка, чтобы не допустить скачки двух потоков для инициализации объекта. Однако, если два потока одновременно пытаются инициализировать объект, и один зависит от другого для завершения, будет циклическая зависимость и взаимоблокировка.

В этом конкретном случае инициализация Go объект может быть завершен только один раз new PrintValueAndWait().printIt(str) завершено. Однако когда param является аргументом по имени, по сути, передается блок кода, который оценивается при его использовании. В этом случае str аргумент в new PrintValueAndWait().printIt(str) это сокращение для Go.strпоэтому, когда поток, на котором работает будущее, пытается оценить param это по сути зовет Go.str, Но с тех пор Go инициализация еще не завершена, он попытается инициализировать Go объект тоже. Инициализация другого потока Go имеет блокировку при инициализации, поэтому будущий поток блокируется. Таким образом, первый поток ожидает завершения будущего, прежде чем завершит инициализацию, а следующий поток ожидает завершения первого потока: deadlock.

В случае по значению строковое значение str передается напрямую, поэтому будущий поток не пытается инициализировать Go и нет тупика.

Точно так же, если вы уходите param как по имени, но меняются Go следующее:

object Go {
  val str = "Rabbits"

  {
    val s = str
    new PrintValueAndWait().printIt(s)
  }
}

он не блокируется, так как уже вычислено локальное строковое значение s передается вместо Go.strпоэтому будущий поток не будет пытаться инициализировать Go,

Другие вопросы по тегам