Nano Hash - криптовалюты, майнинг, программирование

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 являются потоками демона.

Объекты Singleton инициализируются лениво. Каждый одноэлементный объект может быть инициализирован только один раз. Это означает, что первый поток, который обращается к объекту (в вашем случае, основной поток), должен захватить блокировку объекта, а затем инициализировать его. Каждый другой поток, который приходит впоследствии, должен ждать, пока основной поток инициализирует объект и, в конечном итоге, снимет блокировку. Так реализуются одноэлементные объекты в 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 зависает.

02.03.2013
  • Параллельное выполнение блокировки и отложенная инициализация плохо сочетаются друг с другом. Это более общая проблема в Scala (и, если уж на то пошло, в Java). См. этот SIP: docs.scala-lang.org/ глотков/в ожидании/ 20.10.2014
  • Не в качестве возражения, но я думаю, что это ловушка для разработчиков. Я очень благодарен за ваш первоначальный ответ и комментарии, очень. Кстати, я считаю, что ответ был для меня кристально ясен. 30.03.2016
  • Спасибо за добавление ссылки на соответствующий SIP. 30.03.2016
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..