Download presentation
Presentation is loading. Please wait.
1
Concurrency without Actors
Easy Concurrency Concurrency without Actors 20-Jul-18
2
Using the par method scala> val list = (1 to 10).toList list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> list.map(_ + 42) res1: List[Int] = List(43, 44, 45, 46, 47, 48, 49, 50, 51, 52) scala> list.par res2: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> list.par map(_ + 42) res3: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(43, 44, 45, 46, 47, 48, 49, 50, 51, 52) Does not make sense for collections smaller than several thousand.
3
Parallel collections scala.collection.parallel.mutable.ParArray scala.collection.parallel.mutable.ParHashMap scala.collection.parallel.mutable.ParHashSet scala.collection.parallel.mutable.ParTrieMap scala.collection.parallel.immutable.ParVector scala.collection.parallel.immutable.ParHashMap scala.collection.parallel.immutable.ParHashSet scala.collection.parallel.immutable.ParRange
4
map scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) scala> lastNames.map(_.toUpperCase) res4: scala.collection.parallel.immutable.ParSeq[String] = ParVector(SMITH, JONES, FRANKENSTEIN, BACH, JACKSON, RODIN)
5
fold scala> val parArray = (1 to 10000).toArray.par parArray: scala.collection.parallel.mutable.ParArray[Int] = ParArray(1, 2, 3, ... scala> parArray.fold(0)(_ + _) res5: Int =
6
filter scala> val lastNames = List("Smith","Jones","Frankenstein","Bach","Jackson", "Rodin").par lastNames: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Frankenstein, Bach, Jackson, Rodin) scala> lastNames.filter(_.head >= 'J') res6: scala.collection.parallel.immutable.ParSeq[String] = ParVector(Smith, Jones, Jackson, Rodin)
7
Making parallel collections
Create a parallel collection directly (requires import) scala> val pv = ParVector(1, 2, 3, 4, 5) pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(1, 2, 3, 4, 5) Convert from a sequential collection (does not require import) scala> val pv = Vector(1,2,3,4,5).par pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(1, 2, 3, 4, 5)
8
Nondeterminism Side-effecting operations can lead to non-determinism
scala> val list = (1 to 1000).toList.par list: scala.collection.parallel.immutable.ParSeq[Int] = ParVector(1, 2, 3, ... scala> var sum = 0 sum: Int = 0 scala> sum = 0; list.foreach(sum += _) sum: Int = scala> sum = 0; list.foreach(sum += _) sum: Int = scala> sum = 0; list.foreach(sum += _) sum: Int =
9
More nondeterminism Non-associative operations lead to non-determinism
scala> list.reduce(_-_) res12: Int = scala> list.reduce(_-_) res13: Int = 49740 scala> list.reduce(_-_) res14: Int =
10
Associative operations are okay
Non-commutative (but associative) operations are okay scala> val strings = "abc def ghi jk lmnop qrs tuv wx yz".split(" ").par strings: scala.collection.parallel.mutable.ParArray[String] = ParArray(abc, def, ghi, jk, lmnop, qrs, tuv, wx, yz) scala> val alphabet = strings.reduce(_ ++ _) alphabet: String = abcdefghijklmnopqrstuvwxyz
11
Parallel arrays def tabulate[A](end: Int)(f: (Int) ⇒ A): Iterator[A]
Creates an iterator producing the values of a given function over a range of integer values starting from 0. scala> import scala.collection.parallel.mutable.ParArray scala> val pa = ParArray.range(0, 10) pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) scala> val pa = ParArray.tabulate(10)(x => x + 100) pa: scala.collection.parallel.mutable.ParArray[Int] = ParArray(100, 101, 102, 103, 104, 105, 106, 107, 108, 109) Putting the array together again is a sequential process, and may be the slowest part of the operation
12
Some array types Array Same as a Java array, fast for most operations. Scala extends this with ArrayOps, which is full of goodies such as map and filter--but these can be slow for arrays of primitives, as they require boxing and unboxing. WrappedArray Same as a Java array of objects; all operations can be used, but boxing/unboxing is unnecessary, as the result is another WrappedArray. ArraySeq Same as a Java array of objects; primitives are boxed going in and unboxed coming out, but remain boxed for all intermediate operations. ArrayBuffer Acts like an array, but elements can be added or removed.Array ParArray An array that can be operated on in parallel. The .seq method on a ParArray returns an ArraySeq.
13
ParVector scala> val pv = ParVector.range(0, 10) pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) scala> val pv = ParVector.tabulate(10)(x => x + 100) pv: scala.collection.parallel.immutable.ParVector[Int] = ParVector(100, 101, 102, 103, 104, 105, 106, 107, 108, 109)
14
Other types ParVector Fast indexing, fixed size.
.seq returns a Vector. ParRange Example: 1 to 10 par .seq returns a Range. ParHashSet Both mutable and immutable varieties. .seq returns a HashSet. ParHashMap .seq returns a HashMap. concurrent.TrieMap A concurrent thread-safe map. Okay to modify during traversal; updates are only visible in the next iteration.
15
Durations import scala.concurrent.duration._
DurationConversions (included in this package) defines the following methods on numeric types, both integral and real: day days hour hours minute minutes second seconds millisecond milliseconds milli millis microsecond microseconds micro micros nanosecond nanoseconds nano nanos Examples: scala> 3.7 seconds res8: scala.concurrent.duration.FiniteDuration = 3700 milliseconds scala> Math.PI seconds res9: scala.concurrent.duration.FiniteDuration = nanoseconds These methods are optimized for concurrency and are not intended for general use Maximum Duration is about 292 years
16
Simple Futures import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global A Future is a computation that runs concurrently, in a separate Thread The second import above imports the “default global execution context,” which provides the Threads—it is like a Thread Pool Once you have the above imports, actually creating a Future is dead simple: val f = Future { some long slow computation } This sets f to be a Future object, which will (hopefully) at some later time hold a result
17
Blocking on Futures One way to get the result out of a Future is to block and wait for it import scala.concurrent.Await val r = Await.result(f, 1 second) Futures have time limits: scala> { val f = Future { | Thread.sleep(50) | | } | val r = Await.result(f, 40 millis) | println(r) | } java.util.concurrent.TimeoutException: Futures timed out after [40 milliseconds]
18
Blocking is bad The more blocking, the less parallelism, and the less speedup The default execution context only allocates as many threads as you have processors If more threads than that are blocked, computation is deadlocked There are ways to avoid this, but it’s best simply to avoid blocking Only block if nothing more can be done until you have the result of the Future
19
Getting a result without blocking
scala> { val f = Future { | Thread.sleep(50) | | } | println("Before") | f.onComplete { | case Success(value) => println(value) | case Failure(e) => println("Too bad!") | } | println("After") | } Before After scala> 42 The onComplete sets up a callback From this you can see that it also runs in a separate Thread Callbacks can happen in any order, and may not be in the same Thread
20
Using separate callbacks
val f = Future { … } f onSuccess { case result => println(s"Success: $result") } onFailure { case t => println(s"Exception: ${t.getMessage}") } Notice that for all of these (onComplete, onSuccess, onFailure), the result is a partial function This is like the body of a match expression, which is also a partial function
21
Methods that return Futures
def longRunningComputation(i: Int): Future[Int] = Future { sleep(100) i + 1 } // this does not block longRunningComputation(11).onComplete { case Success(result) => println(s"result = $result") case Failure(e) => e.printStackTrace } // important: keep the jvm from shutting down sleep(1000)
22
Combining Futures scala> val f1 = Future { 42} f1: scala.concurrent.Future[Int] = Success(42) val f2 = Future {10000} f2 : scala.concurrent.Future[Int] = List() val fsum = f1.onSuccess { case x => f2.onSuccess { case y => x + y } }
23
Reference This entire slide set is pretty much a direct translation from
24
The End
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.