Piotr Gawryś
https://github.com/Avasil
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
Heap
Thread Stack
Objects
call stack
local variables
Thread Stack
call stack
local variables
Thread Stack
call stack
local variables
twitter.com/p_gawrys
CPU's Core 1
Main Memory
registers
CPU
Cache
JVM
CPU's Core 3
registers
CPU
Cache
CPU's Core 2
registers
CPU
Cache
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
import java.util.concurrent.Executors
import monix.execution.Scheduler
import scala.concurrent.ExecutionContext
val ec = ExecutionContext.fromExecutor(Executors.newCachedThreadPool())
val scheduler = Scheduler(ec)
twitter.com/p_gawrys
import monix.execution.schedulers.TestScheduler
import scala.concurrent.duration._
import cats.implicits._
val sc = TestScheduler()
val failedTask: Task[Int] = Task.sleep(2.days) >>
Task.raiseError[Int](new Exception("boom"))
val f: CancelableFuture[Int] = failedTask.runToFuture(sc)
println(f.value) // None
sc.tick(10.hours)
println(f.value) // None
sc.tick(1000.days)
println(f.value) // Some(Failure(java.lang.Exception: boom))
twitter.com/p_gawrys
twitter.com/p_gawrys
val s: Scheduler = Scheduler.global
def repeat: Task[Unit] =
for {
_ <- Task.shift
_ <- Task(println(s"Shifted to: ${Thread.currentThread().getName}"))
_ <- repeat
} yield ()
repeat.runToFuture(s)
twitter.com/p_gawrys
// Output:
// Shifted to: scala-execution-context-global-12
// Shifted to: scala-execution-context-global-12
// Shifted to: scala-execution-context-global-12
// Shifted to: scala-execution-context-global-14
// Shifted to: scala-execution-context-global-14
// Shifted to: scala-execution-context-global-14
// Shifted to: scala-execution-context-global-12
// Shifted to: scala-execution-context-global-12
// Shifted to: scala-execution-context-global-13
// Shifted to: scala-execution-context-global-13
// Shifted to: scala-execution-context-global-13
// Shifted to: scala-execution-context-global-12
// Shifted to: scala-execution-context-global-12
// ...
twitter.com/p_gawrys
val s1: Scheduler = Scheduler(
ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor()),
ExecutionModel.SynchronousExecution)
def repeat(id: Int): Task[Unit] =
Task(print(id)).flatMap(_ => repeat(id))
val prog = (repeat(1), repeat(2)).parTupled
prog.runToFuture(s1)
// Output:
// 1111111111111111111111111111111111111111111111111111111111...
twitter.com/p_gawrys
val s1: Scheduler = Scheduler(
ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor()),
ExecutionModel.SynchronousExecution)
def repeat(id: Int): Task[Unit] =
Task(print(id)).flatMap(_ => Task.shift >> repeat(id))
val prog = (repeat(1), repeat(2)).parTupled
prog.runToFuture(s1)
// Output:
// 121212121212121212121212121212121212121212121 ...
twitter.com/p_gawrys
val s1: Scheduler = Scheduler( // 4 = number of cores on my laptop
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4)),
ExecutionModel.SynchronousExecution)
val s2: Scheduler = Scheduler(
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(1)),
ExecutionModel.SynchronousExecution)
def repeat(id: Int): Task[Unit] =
Task(print(id)).flatMap(_ => repeat(id))
val program = (repeat(1), repeat(2), repeat(3), repeat(4), repeat(5),
repeat(6).executeOn(s2)).parTupled
program.runToFuture(s1)
// Output:
// 143622331613424316134424316134243161342431613424316134243 ...
// no '5' !
twitter.com/p_gawrys
twitter.com/p_gawrys
implicit val s = Scheduler.global
.withExecutionModel(ExecutionModel.SynchronousExecution)
def task(i: Int): Task[Unit] =
Task(println(s"$i: ${Thread.currentThread().getName}")) >>
Task.shift(TrampolineExecutionContext.immediate) >> task(i + 1)
val t =
for {
fiber <- task(0)
.doOnCancel(Task(println("cancel")))
.start
_ <- fiber.cancel
} yield ()
t.runToFuture
// Output:
// 0: scala-execution-context-global-11
// 1: scala-execution-context-global-11
// 2: scala-execution-context-global-11
// cancel
twitter.com/p_gawrys
val immediate: TrampolineExecutionContext =
TrampolineExecutionContext(new ExecutionContext {
def execute(r: Runnable): Unit = r.run()
def reportFailure(e: Throwable): Unit = throw e
})
twitter.com/p_gawrys
twitter.com/p_gawrys
implicit val globalScheduler = Scheduler.global
val blockingScheduler = Scheduler.io()
val blockingOp = Task {
Thread.sleep(1000)
println(s"${Thread.currentThread().getName}: done blocking")
}
val cpuBound = Task {
// keep cpu busy
println(s"${Thread.currentThread().getName}: done calculating")
}
val t =
for {
_ <- cpuBound
_ <- blockingOp.executeOn(blockingScheduler)
_ <- cpuBound
} yield ()
t.runSyncUnsafe()
// scala-execution-context-global-11: done calculating
// monix-io-12: done blocking
// scala-execution-context-global-11: done calculating
twitter.com/p_gawrys
val ec = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor())
implicit val scheduler = Scheduler(ec)
val otherTask = Task.sleep(50.millis) >> Task(println("Running concurrently"))
val t =
for {
sem <- Semaphore[Task](0L)
// start means it will run asynchronously "in the background"
// and the next step in for comprehension will begin
_ <- (Task.sleep(100.millis) >>
Task(println("Releasing semaphore")) >> sem.release).start
_ <- Task(println("Waiting for permit")) >> sem.acquire
_ <- Task(println("Done!"))
} yield ()
(t, otherTask).parTupled.runSyncUnsafe()
// Waiting for permit
// Running concurrently
// Releasing semaphore
// Done!
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
implicit val s = Scheduler.global
def foo(i: Int): Task[Unit] =
for {
_ <- Task(println(s"start $i"))
_ <- if (i % 2 == 0) Task.raiseError(DummyException("error"))
else Task.sleep(10.millis)
_ <- Task(println(s"end $i"))
} yield ()
val tasks: List[Task[Unit]] = List(foo(1), foo(2), foo(3), foo(4))
val result: Task[List[Unit]] = Task.gather(tasks)
println(result.attempt.runSyncUnsafe())
// start 4
// start 1
// start 2
// start 3
// Left(monix.execution.exceptions.DummyException: error)
twitter.com/p_gawrys
// (...) code from the previous slide
val tasks: List[Task[Unit]] = List(foo(1), foo(2), foo(3), foo(4))
val result: Task[List[Either[Throwable, Unit]]] = Task.wander(tasks)(_.attempt)
println(result.runSyncUnsafe())
// start 2
// start 1
// start 3
// start 4
// end 1
// end 3
// List(
// Right(()), Left(monix.execution.exceptions.DummyException: error),
// Right(()), Left(monix.execution.exceptions.DummyException: error)
// )
twitter.com/p_gawrys
implicit val s = Scheduler.global
val t1 =
for {
_ <- Task(println("t1: start"))
_ <- Task.sleep(100.millis)
_ <- Task(println("t1: middle"))
_ <- Task.sleep(100.millis)
_ <- Task(println("t1: end"))
} yield ()
t1.timeout(150.millis).runSyncUnsafe()
// t1: start
// t1: middle
// Exception in thread "main" java.util.concurrent.TimeoutException:
// Task timed-out after 150 milliseconds of inactivity
twitter.com/p_gawrys
val ec = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor())
implicit val scheduler = Scheduler(ec)
def middle: Task[Unit] = Task {
while(true){}
}
val t1 =
for {
_ <- Task(println("t1: start"))
_ <- Task.sleep(100.millis)
_ <- middle >> Task(println("t1: middle"))
_ <- Task.sleep(100.millis)
_ <- Task(println("t1: end"))
} yield ()
t1.timeout(150.millis).runSyncUnsafe()
// t1: start
// ... never stops running
Thread is scheduled by VM instead of OS. Cheap to start and we can map M Green Threads to N OS Threads.
"Lightweight thread". Fibers voluntarily yield control to the scheduler.
twitter.com/p_gawrys
twitter.com/p_gawrys
twitter.com/p_gawrys
trait Fiber[F[_], A] {
def cancel: F[Unit]
def join: F[A]
}
sealed abstract class Task[+A] {
// (...)
final def start: Task[Fiber[A]]
// (...)
}
https://gitter.im/typelevel/cats-effect
https://gitter.im/functional-streams-for-scala/fs2
https://gitter.im/monix/monix
https://gitter.im/ZIO/core
If you're looking for help/discussion:
twitter.com/p_gawrys