Concurrency
&
Parallelism
Artūras Šlajus
CTO @ Tiny Lab Productions
arturas.slajus@gmail.com
What is concurrency?
What is parallelism?
How do they differ?
A deeper look
We have a task.
Dig 2 holes in the ground.
Each hole takes 10 ticks to dig.
Let's take 3 approaches:
- sequential
- concurrent
- parallel
Sequential
Yay DOS!
1 1 1 1 1 2
0 2 4 6 8 0 2 4 6 8 0
Hole 1 XXXXXXXXXX
Context Switching X
Hole 2 XXXXXXXXXX
Total ticks: 21
The problem?
Responsiveness.
Concurrent
Yay modern OSes!
1 1 1 1 1 2 2 2 2
0 2 4 6 8 0 2 4 6 8 0 2 4 6
Hole 1 XXX XXX XXX X
Context Switching X X X X X X X
Hole 2 XXX XXX XXX X
Total ticks: 27
The problem?
It takes longer.
Parallel
Yay multicore CPUs!
1 1 1 1 1 2 2 2 2
0 2 4 6 8 0 2 4 6 8 0 2 4 6
Hole 1 XXXXXXXXXX
Hole 2 XXXXXXXXXX
Total ticks: 10
The problem?
It's hard to do.
Real life
arturas@razor:~$ irb
irb(main):001:0> a = 0
=> 0
irb(main):002:0> 10.times { Thread.new { 100.times { a += 1 } } }
=> 10
irb(main):003:0> a
=> 1000
arturas@razor:~$ scala
Welcome to Scala version 2.10.3 (OpenJDK 64-Bit Server VM, Java 1.7.0_51).
scala> import java.lang._
scala> var a = 0
a: Int = 0
scala> (0 to 9).foreach { _ => new Thread(new Runnable {
def run = (0 to 99).foreach { _ => a += 1 }
}).start }
scala> a
res1: Int = 565
How to fix it
University edition
scala> val mutex = new Object
mutex: Object = java.lang.Object@64b51464
scala> (0 to 9).foreach { _ => new Thread(new Runnable {
def run = (0 to 99).foreach { _ => mutex.synchronized { a += 1 } }
}).start }
scala> a
res10: Int = 1000
Right?
Not quite
How to avoid deadlocks
Simple.
Always lock everything in the same order.
But hard to do.
Solutions?
Futures, Promises & Execution Contexts
Actor model
Future is now
Future is a read-many placeholder for a value that might be available some time in the future.
Futures!
scala> import concurrent._, ExecutionContext.Implicits.global
scala> val listOfFutures = (0 to 9).map { _ => Future { 100 } }
listOfFutures: List[Future[Int]]
scala> val futureOfLists = Future.sequence(listOfFutures)
futureOfLists: Future[List[Int]]
scala> val futureOfSum = futureOfLists.map(_.sum)
futureOfSum: Future[Int]
scala> futureOfSum.value
res0: Option[scala.util.Try[Int]] = Some(Success(1000))
How does it work exactly?
The simple (non-parallel) version
trait MyPromise[A] {
def complete(value: A)
def future: MyFuture[A]
def value: Option[A]
}
trait MyFuture[A] {
def onComplete(f: A => Unit)
def value: Option[A]
}
Implementation
class MyPromiseFuture[A] extends MyPromise[A] with MyFuture[A] {
private[this] var _value = Option.empty[A]
private[this] var listeners = List.empty[A => Unit]
def future = this
def complete(value: A) = {
if (_value.isDefined)
throw new IllegalStateException("Promise is already completed!")
_value = Some(value)
listeners.foreach(_(value))
listeners = List.empty
}
def value = _value
def onComplete(f: A => Unit) = value.fold(listeners ::= f)(f)
}
but what's the point?
One of the uses is turning callback style APIs into Future APIs.
val http: Http
val p = new MyPromiseFuture[Response]
http.doRequest(...).onComplete(p.complete)
p.future
Is it better?
Not for now. But add some magic sauce...
trait MyFuture[A] {
def map[B](f: A => B): MyFuture[B]
def flatMap[B](f: A => MyFuture[B]): MyFuture[B]
def zip[B](other: MyFuture[B]): MyFuture[(A, B)]
}
And implement it...
class MyPromiseFuture[A] extends MyPromise[A] with MyFuture[A] {
def map[B](f: A => B) = {
val p = new MyPromiseFuture[B]
onComplete(f andThen p.complete)
p.future
}
def flatMap[B](f: A => MyFuture[B]) = {
val p = new MyPromiseFuture[B]
onComplete(a => f(a).onComplete(p.complete))
p.future
}
def zip[B](other: MyFuture[B]) = {
val p = new MyPromiseFuture[(A, B)]
onComplete(a => other.value.foreach(b => p.complete(a, b)))
other.onComplete(b => value.foreach(a => p.complete(a, b)))
p.future
}
}
And you can do this stuff
val f1 = http.futureRequest(...)
val f2 = http.futureRequest(...)
val final = f1.zip(f2).flatMap { case (r1, r2) =>
http.futureRequest(r1.dataA, r2.dataB)
}.map { r3 => r3.dataC }
Kind of neat, huh?
Use it right now
Scala: scala.concurrent.Future
Java: java.util.concurrent.Future
(but really just use Scala futures)
Javascript: https://github.com/FuturesJS/FuturesJS
.NET: System.Threading.Tasks.Task
lets do actors
Futures are nice and everything.
But we need shared mutable state.
How do we share state?
Hierarchy
Real code
import akka.actor.{ActorSystem, Props, ActorRef, Actor}
object Counter {
case class Inc(by: Int)
case object Current
case class CurrentReply(count: Int)
}
class Counter(private[this] var count: Int) extends Actor {
def receive = {
case Counter.Inc(by) => count += by
case Counter.Current => sender ! Counter.CurrentReply(count)
}
}
class Increaser(manager: ActorRef, counter: ActorRef) extends Actor {
(1 to 100).foreach { _ => counter ! Counter.Inc(1) }
counter ! Counter.Current
def receive = { case msg => manager ! msg }
}
class Manager extends Actor {
val counter = context.actorOf(Props(classOf[Counter], 0), "counter")
val children = (1 to 10).map { i => context.actorOf(
Props(classOf[Increaser], self, counter), s"increaser-$i"
) }
def receive = {
case Counter.CurrentReply(count) =>
println(s"$sender reported count: $count")
}
}
object Main {
def main(args: Array[String]) {
val system = ActorSystem("main")
system.actorOf(Props(classOf[Manager]), "manager")
Console.readLine()
}
}
Real Output
[info] Running Main
Actor[akka://main/user/manager/increaser-3#354246439] reported count: 319
Actor[akka://main/user/manager/increaser-5#-1720444000] reported count: 365
Actor[akka://main/user/manager/increaser-6#-453137600] reported count: 509
Actor[akka://main/user/manager/increaser-1#-481790462] reported count: 601
Actor[akka://main/user/manager/increaser-2#-2132540929] reported count: 622
Actor[akka://main/user/manager/increaser-4#-448697138] reported count: 770
Actor[akka://main/user/manager/increaser-7#-1979722102] reported count: 817
Actor[akka://main/user/manager/increaser-8#-1507785331] reported count: 856
Actor[akka://main/user/manager/increaser-9#-761390070] reported count: 986
Actor[akka://main/user/manager/increaser-10#-1851044690] reported count: 1000
So that's the basics
What is nice about actors?
- They encapsulate mutable state.
- They run in parallel automatically.
- They are simple to reason about.
- They don't care where they are (e.g. remote machines).
- They handle failure gracefully (actor supervision).
- They can interact with non-actor code (e.g. akka typed actors).
Where do I get it?
Scala / Java: www.akka.io
Ruby: www.celluloid.io
Python: www.pykka.org
.NET: https://code.google.com/p/n-act/
PHP: not yet
Thank you
Questions?
CTO @ Tiny Lab Productions
arturas.slajus@gmail.com
https://github.com/arturaz/concurrency-presentation
Concurrency & Parallelism
By Artūras Šlajus
Concurrency & Parallelism
- 5,417