Scala: параллельный сбор в инициализаторе объекта приводит к зависанию программы
Я только что заметил тревожное поведение. Допустим, у меня есть отдельная программа, состоящая из единственного объекта:
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.
1 ответ
Это неотъемлемая проблема, которая может возникнуть в Scala при освобождении ссылки на одноэлементный объект до завершения строительства. Это происходит из-за того, что другой поток пытается получить доступ к объекту ParCollectionInInitializerTest
прежде чем он был полностью построен. Это не имеет ничего общего с main
скорее, это связано с инициализацией объекта, который содержит main
метод - попробуйте запустить это в REPL, набрав в выражении ParCollectionInInitializerTest
и вы получите те же результаты. Это также не имеет никакого отношения к рабочим потокам fork-join, являющимся потоками демонов.
Одиночные объекты инициализируются лениво. Каждый одноэлементный объект может быть инициализирован только один раз. Это означает, что первый поток, который обращается к объекту (в вашем случае основной поток) должен захватить блокировку объекта, а затем инициализировать его. Каждый последующий поток должен ждать, пока основной поток инициализирует объект и, в конце концов, снимет блокировку. Именно так в 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 висит.