This presentation explains the difference between concurrency and parallelism, and how could we make parallel computations and how could we design an API for parallel computation following the structure presented in the Functional Programming in Scala book
8. 8
Place your screenshot here
Parallelism
Image from: https://computing.llnl.gov/tutorials/parallel_comp/
Parallelism vs Concurrency
9. Concurrency is the ability for many tasks to be
executed out-of-order and coordinate the
result.
9
Parallelism vs Concurrency
10. 10
Place your screenshot here
Concurrency
Image from: http://adit.io/posts/2013-05-11-The-Dining-Philosophers-Problem-With-Ron-Swanson.html
Parallelism vs Concurrency
11. Concurrency
- Dealing with MANY
things at ONCE
- Structure
- Communication with
independent
components and
coordinating with them
Parallelism
- Doing MANY things at
ONCE
- Execution
- Executing simultaneous
processes
11
Parallelism vs ConcurrencyParallelism vs Concurrency
Summary
13. Thread
✘ Thread is a linear flow of execution
✘ Managed by a Scheduler
13
Runnable vs Callable vs Future
14. Thread
✘ Thread is a linear flow of execution
✘ Managed by a Scheduler
14
Runnable vs Callable vs Future
def task() =
new Thread(() =>
println(s"Hi Thread: ${Thread.currentThread.getName}"))
task().start()
15. Thread
✘ Thread is a linear flow of execution
✘ Managed by a Scheduler
15
Runnable vs Callable vs Future
def task() =
new Thread(() =>
println(s"Hi Thread: ${Thread.currentThread.getName}"))
task().start()
✘ 1 Thread = 1 OS Thread
22. Runnable
✘ Define tasks that extend Runnable
22
Runnable vs Callable vs Future
val t1 = task()
val t2 = task()
val t3 = task()
23. Runnable
✘ Define tasks that extend Runnable
✘ Submit the parallel tasks
23
Runnable vs Callable vs Future
service.submit(t1)
service.submit(t2)
service.submit(t3)
24. Runnable
✘ Define tasks that extend Runnable
✘ Submit the parallel tasks
24
Runnable vs Callable vs Future
How could we get meaningful values from the
separate Threads?
25. Runnable
25
Runnable vs Callable vs Future
trait Runnable { def run: Unit }
✘ Define tasks that extend Runnable
✘ Submit the parallel tasks
How could we get meaningful values from the
separate Threads?
26. Runnable
26
Runnable vs Callable vs Future
class ExecutorService {
def submit(a: Runnable): Future[Unit]
def submit[A](a: Callable[A]): Future[A]
}
27. Runnable
27
Runnable vs Callable vs Future
class ExecutorService {
def submit(a: Runnable): Future[Unit]
def submit[A](a: Callable[A]): Future[A]
}
29. Callable
✘ Define tasks that extend Callable
29
Runnable vs Callable vs Future
def increment(i: Int) =
new Callable[Int] {
def call(): Int = i +1
}
30. Callable
✘ Define tasks that extend Callable
✘ Submit the parallel tasks
30
Runnable vs Callable vs Future
import java.util.concurrent.Future
val v1: Future[Int] = service.submit(increment(1))
val v2: Future[Int] = service.submit(increment(2))
31. Callable
✘ Define tasks that extend Callable
✘ Submit the parallel tasks
31
Runnable vs Callable vs Future
import java.util.concurrent.Future
val v1: Future[Int] = service.submit(increment(1))
val v2: Future[Int] = service.submit(increment(2))
How could we use the value of every Future?
32. Callable
✘ Define tasks that extend Callable
✘ Submit the parallel tasks
32
Runnable vs Callable vs Future
import java.util.concurrent.Future
val v1: Future[Int] = service.submit(increment(1))
val v2: Future[Int] = service.submit(increment(2))
How could we use the value of every Future?
trait Future[A] { def get: A }
33. Callable
✘ Define tasks that extend Callable
✘ Submit the parallel tasks
33
Runnable vs Callable vs Future
import java.util.concurrent.Future
val v1: Future[Int] = service.submit(increment(1))
val v2: Future[Int] = service.submit(increment(2))
STATEMENTS
34. ✘ Define futures
Future
34
Runnable vs Callable vs Future
def increment(i: Int) = Future.successful(i + 1)
val f1: Future[Int] = increment(1)
val f2: Future[Int] = increment(2)
val result: Future[Int] = for {
v1 <- f1
v2 <- f2
} yield v1 + v2
35. ✘ Define futures
Future
35
Runnable vs Callable vs Future
def increment(i: Int) = Future.successful(i + 1)
val f1: Future[Int] = increment(1)
val f2: Future[Int] = increment(2)
val result: Future[Int] = for {
v1 <- f1
v2 <- f2
} yield v1 + v2
Cannot find an implicit ExecutionContext. You might pass
[error] an (implicit ec: ExecutionContext) parameter to your method
[error] or import scala.concurrent.ExecutionContext.Implicits.global.
[error] v2 <- f2
[error] ^
36. ✘ Define futures
Future
36
Runnable vs Callable vs Future
import scala.concurrent.ExecutionContext.Implicits.global
def increment(i: Int) = Future.successful(i + 1)
val f1: Future[Int] = increment(1)
val f2: Future[Int] = increment(2)
val result: Future[Int] = for {
v1 <- f1
v2 <- f2
} yield v1 + v2
41. 41
The Data Type for parallel computations
Pure Future
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
42. 42
The Data Type for parallel computations
Pure Future
type Par[A] = ExecutorService => Future[A]
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
43. 43
The Data Type for parallel computations
Pure Future
type Par[A] = ExecutorService => Future[A]
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
44. 44
The Data Type for parallel computations
Pure Future
type Par[A] = ExecutorService => Future[A]
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
45. 45
The Data Type for parallel computations
Pure Future
object Par {
def unit[A](a: A): Par[A] = ???
}
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
46. 46
Par.unit
Pure Future
type Par[A] = ExecutorService => Future[A]
object Par {
def unit[A](a: A): Par[A] = (_: ExecutorService) =>
new Future[A] {
def apply(cb: A => Unit): Unit = cb(a)
}
}
47. 47
The Data Type for parallel computations
Pure Future
object Par {
def fork[A](a: Par[A]): Par[A] = ???
}
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
48. 48
Par.fork
Pure Future
type Par[A] = ExecutorService => Future[A]
object Par {
def fork[A](a: Par[A]): Par[A] = es =>
new Future[A] {
def apply(cb: A => Unit): Unit =
es.submit(new Callable[Unit] {
def call: Unit = a(es)(cb) // a(es).apply(cb)
})
}
}
49. 49
The Data Type for parallel computations
Pure Future
object Par {
def run[A](es: ExecutorService)(par: Par[A]): A =
???
}
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
50. 50
Par.run
Pure Future
type Par[A] = ExecutorService => Future[A]
object Par {
def run[A](es: ExecutorService)(p: Par[A]): A = {
val ref = new AtomicReference[A]
val latch = new CountDownLatch(1)
p(es) { a =>
ref.set(a)
latch.countDown()
}
latch.await()
ref.get
}
}
51. 51
Par.run
Pure Future
type Par[A] = ExecutorService => Future[A]
object Par {
def run[A](es: ExecutorService)(p: Par[A]): A = {
val ref = new AtomicReference[A]
val latch = new CountDownLatch(1)
p(es) { a =>
ref.set(a)
latch.countDown()
}
latch.await()
ref.get
}
}
val one = Par.unit(1)
run(es)(one) ⇒ 1
52. “Programming with mutability is
like working with the
mother-in-law who’s waiting you to
fail” - Venkat Subramaniam
52
53. object Par {
def map[A, B](a: Par[A])(f: A => B): Par[B]
def flatMap[A, B](a: Par[A])(f: A => Par[B]): Par[B]
def parMap[A, B](l: List[A])(f: A => B): Par[List[B]]
def map2[A, B, C](a: Par[A], b: Par[B])(f: (A, B) => C): Par[C]
...
}
53
The Data Type for parallel computations
Pure Future
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
54. 54
The Data Type for parallel computations
Pure Future
✘ Requires ExecutorService
✘ Asynchronously performs a given computation of any type
✘ Contains a result of the computation
✘ Composable
object Par {
def map[A, B](a: Par[A])(f: A => B): Par[B]
def flatMap[A, B](a: Par[A])(f: A => Par[B]): Par[B]
def parMap[A, B](l: List[A])(f: A => B): Par[List[B]]
def map2[A, B, C](a: Par[A], b: Par[B])(f: (A, B) => C): Par[C]
...
}