Scala Language
Kolekcje równoległe
Szukaj…
Uwagi
Kolekcje równoległe ułatwiają programowanie równoległe, ukrywając szczegóły paralelizacji niskiego poziomu. Ułatwia to korzystanie z architektur wielordzeniowych. Przykłady równoległych kolekcji obejmują ParArray
, ParVector
, mutable.ParHashMap
, immutable.ParHashMap
i ParRange
. Pełna lista znajduje się w dokumentacji .
Tworzenie i używanie równoległych kolekcji
Aby utworzyć kolekcję równoległą z kolekcji sekwencyjnej, wywołaj metodę par
. Aby utworzyć kolekcję sekwencyjną z kolekcji równoległej, wywołaj metodę seq
. Ten przykład pokazuje, w jaki sposób zamieniasz zwykły Vector
w ParVector
, a następnie ponownie:
scala> val vect = (1 to 5).toVector
vect: Vector[Int] = Vector(1, 2, 3, 4, 5)
scala> val parVect = vect.par
parVect: scala.collection.parallel.immutable.ParVector[Int] = ParVector(1, 2, 3, 4, 5)
scala> parVect.seq
res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5)
Metodę par
można połączyć w łańcuch, umożliwiając konwersję kolekcji sekwencyjnej na kolekcję równoległą i natychmiastowe wykonanie na niej akcji:
scala> vect.map(_ * 2)
res1: scala.collection.immutable.Vector[Int] = Vector(2, 4, 6, 8, 10)
scala> vect.par.map(_ * 2)
res2: scala.collection.parallel.immutable.ParVector[Int] = ParVector(2, 4, 6, 8, 10)
W tych przykładach praca jest faktycznie podzielona na wiele jednostek przetwarzania, a następnie ponownie dołączona po zakończeniu pracy - bez konieczności interwencji programisty.
Pułapki
Nie należy używać kolekcji równoległych, gdy elementy kolekcji muszą być odbierane w określonej kolejności.
Kolekcje równoległe wykonują operacje jednocześnie. Oznacza to, że cała praca jest podzielona na części i rozdzielona między różne procesory. Każdy procesor nie jest świadomy pracy wykonywanej przez innych. Jeśli kolejność kolekcji ma znaczenie, praca przetwarzana równolegle nie jest deterministyczna. (Dwukrotne uruchomienie tego samego kodu może dać różne wyniki).
Operacje niezwiązane z asocjacją
Jeśli operacja nie ma asocjacji (jeśli kolejność wykonywania ma znaczenie), wynik w równoległej kolekcji będzie niedeterministyczny.
scala> val list = (1 to 1000).toList
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10...
scala> list.reduce(_ - _)
res0: Int = -500498
scala> list.reduce(_ - _)
res1: Int = -500498
scala> list.reduce(_ - _)
res2: Int = -500498
scala> val listPar = list.par
listPar: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10...
scala> listPar.reduce(_ - _)
res3: Int = -408314
scala> listPar.reduce(_ - _)
res4: Int = -422884
scala> listPar.reduce(_ - _)
res5: Int = -301748
Skutki uboczne
Operacje, które mają skutki uboczne, takie jak foreach
, mogą nie być wykonywane zgodnie z potrzebami w równoległych kolekcjach z powodu warunków wyścigu. Unikaj tego, używając funkcji, które nie mają skutków ubocznych, takich jak reduce
lub map
.
scala> val wittyOneLiner = Array("Artificial", "Intelligence", "is", "no", "match", "for", "natural", "stupidity")
scala> wittyOneLiner.foreach(word => print(word + " "))
Artificial Intelligence is no match for natural stupidity
scala> wittyOneLiner.par.foreach(word => print(word + " "))
match natural is for Artificial no stupidity Intelligence
scala> print(wittyOneLiner.par.reduce{_ + " " + _})
Artificial Intelligence is no match for natural stupidity
scala> val list = (1 to 100).toList
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15...