Почему 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
,