Является ли этот параллельный массив scala потокобезопасным?
Я хочу использовать параллельные массивы для задачи, и прежде чем я начну с кодирования, мне было бы интересно узнать, является ли этот небольшой фрагмент кода потокобезопасным:
import collection.mutable._
var listBuffer = ListBuffer[String]("one","two","three","four","five","six","seven","eight","nine")
var jSyncList = java.util.Collections.synchronizedList(new java.util.ArrayList[String]())
listBuffer.par.foreach { e =>
println("processed :"+e)
// using sleep here to simulate a random delay
Thread.sleep((scala.math.random * 1000).toLong)
jSyncList.add(e)
}
jSyncList.toArray.foreach(println)
Существуют ли более эффективные способы обработки чего-либо с параллельными коллекциями и сбора результатов в других местах?
5 ответов
Код, который вы разместили, совершенно безопасен; Я не уверен насчет предпосылки: зачем вам нужно накапливать результаты параллельного сбора в непараллельный? Одним из достоинств параллельных коллекций является то, что они похожи на другие коллекции.
Я думаю, что параллельные коллекции также обеспечат seq
способ переключения на последовательные. Так что вы, вероятно, должны использовать это!
Чтобы этот шаблон был безопасным:
listBuffer.par.foreach { e => f(e) }
f
должен быть в состоянии одновременно работать безопасным способом. Я думаю, что применяются те же правила, которые необходимы для безопасной многопоточности (доступ к общему состоянию должен быть поточно-безопасным, порядок f
призывает к разным e
не будет детерминированным, и вы можете столкнуться с тупиками, когда вы начнете синхронизировать свои заявления в f
).
Кроме того, мне неясно, что дает вам параллельная коллекция, когда вы изменяете базовую коллекцию во время обработки, поэтому изменяемый буфер списка, в котором могут быть добавлены / удалены элементы, возможно, является плохим выбором. Никогда не знаешь, когда следующий кодер назовет что-то вроде foo(listBuffer)
до вашего foreach
и передать эту ссылку другому потоку, который может изменить список во время его обработки.
Кроме этого, я думаю, что для любого f
это займет много времени, может быть вызвано одновременно и где e
могут быть обработаны не по порядку, это прекрасный шаблон.
immutCol.par.foreach { e => threadSafeOutOfOrderProcessingOf(e) }
Отказ от ответственности: я не пытался // собрать себя, но я с нетерпением жду, чтобы ТАК вопросы / ответы показали нам, что работает хорошо.
Этот код довольно странный - зачем добавлять материал параллельно чему-то, что нужно синхронизировать? Вы добавите раздор и ничего не получите взамен.
Принцип действия - накапливать результаты параллельной обработки, лучше достичь с такими вещами, как fold
, reduce
или же aggregate
,
synchronisedList
должен быть безопасным, хотя println
может дать неожиданные результаты - у вас нет гарантии того, что элементы будут напечатаны, или даже то, что ваши отпечатки не будут чередоваться в середине символа.
Синхронизированный список также вряд ли будет самым быстрым способом сделать это, более безопасное решение - map
над неизменной коллекцией (Vector
это, вероятно, ваша лучшая ставка здесь), затем напечатайте все строки (по порядку):
val input = Vector("one","two","three","four","five","six","seven","eight","nine")
val output = input.par.map { e =>
val msg = "processed :" + e
// using sleep here to simulate a random delay
Thread.sleep((math.random * 1000).toLong)
msg
}
println(output mkString "\n")
Вы также заметите, что этот код имеет столько же практической полезности, что и ваш пример:)
Код, который вы разместили, безопасен - не будет ошибок из-за несогласованного состояния вашего списка массивов, потому что доступ к нему синхронизирован.
Тем не менее, параллельные коллекции обрабатывают элементы одновременно (одновременно) И не в порядке. Вне порядка означает, что элемент 54. может быть обработан до элемента 2. Ваш список синхронизированных массивов будет содержать элементы в неопределяемом порядке.
В общем лучше использовать map
, filter
и другие функциональные комбинаторы для преобразования коллекции в другую коллекцию - это обеспечит сохранение гарантий упорядочения, если в коллекции есть некоторые (например, Seq
с) Например:
ParArray(1, 2, 3, 4).map(_ + 1)
всегда возвращается ParArray(2, 3, 4, 5)
,
Однако, если вам нужен определенный потокобезопасный тип коллекции, такой как ConcurrentSkipListMap
или синхронизированную коллекцию, которая будет передана какому-либо методу в некотором API, модификация его из параллельного foreach безопасна.
Наконец, параллельный сбор примечаний обеспечивает параллельные массовые операции с данными. Изменяемые параллельные коллекции не являются поточно-ориентированными в том смысле, что вы можете добавлять к ним элементы из разных потоков. Изменяемые операции, такие как вставка на карту или добавление буфера, все еще должны быть синхронизированы.