Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Parallel Patterns

Parallel programming in Scala can be achieved using various libraries and constructs that leverage the power of modern multi-core processors. Below are some common parallel programming patterns and techniques in Scala, including examples using Scala's Future, parallel collections, and Akka.

1. Futures and Promises

Futures provide a way to handle asynchronous computations. They can be used to perform operations in parallel and handle the results once they are completed.

import scala.concurrent._
import ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.{Success, Failure}

def slowComputation(x: Int): Int = {
  Thread.sleep(1000) // Simulate a long computation
  x * x
}

// Creating a Future
val futureResult: Future[Int] = Future {
  slowComputation(10)
}

// Handling the result
futureResult.onComplete {
  case Success(value) => println(s"Result: $value")
  case Failure(exception) => println(s"Failed: $exception")
}

// Waiting for the future to complete (for demonstration purposes only, typically avoid blocking)
Await.result(futureResult, 2.seconds)

2. Parallel Collections

Parallel collections provide a simple way to parallelize operations on collections.

import scala.collection.parallel.CollectionConverters._

val list = List(1, 2, 3, 4, 5)

// Using parallel collections
val parallelList = list.par
val result = parallelList.map(_ * 2)

// Converting back to sequential collection
val sequentialResult = result.seq

// Usage
println(sequentialResult) // List(2, 4, 6, 8, 10)

3. Akka Actors

Akka provides a powerful model for building concurrent, distributed, and fault-tolerant applications using actors.

First, add the Akka dependencies to your build.sbt:

libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.20"

Then, you can define and use actors:

import akka.actor._

case object Start
case class Compute(x: Int)

class Worker extends Actor {
  def receive: Receive = {
    case Compute(x) =>
      val result = x * x
      println(s"Computed $result for $x")
  }
}

class Master extends Actor {
  val worker = context.actorOf(Props[Worker], "worker")

  def receive: Receive = {
    case Start =>
      for (i <- 1 to 5) worker ! Compute(i)
  }
}

// Creating the actor system
val system = ActorSystem("MyActorSystem")
val master = system.actorOf(Props[Master], "master")

// Starting the computation
master ! Start

// Shutting down the system
system.terminate()

4. Parallel Execution with Future Combinators

You can also combine multiple futures to perform parallel operations and combine their results.

import scala.concurrent._
import ExecutionContext.Implicits.global
import scala.util.{Success, Failure}

val future1 = Future { slowComputation(10) }
val future2 = Future { slowComputation(20) }
val future3 = Future { slowComputation(30) }

// Combining futures
val combinedFuture = for {
  result1 <- future1
  result2 <- future2
  result3 <- future3
} yield result1 + result2 + result3

// Handling the result
combinedFuture.onComplete {
  case Success(value) => println(s"Combined result: $value")
  case Failure(exception) => println(s"Failed: $exception")
}

// Waiting for the combined future to complete (for demonstration purposes only, typically avoid blocking)
Await.result(combinedFuture, 5.seconds)

5. Parallel Streams with Akka Streams

Akka Streams provide a powerful way to handle streaming data and back-pressure. Add the Akka Streams dependency to your build.sbt:

libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.6.20"

Then, you can define and run a stream:

import akka.actor.ActorSystem
import akka.stream._
import akka.stream.scaladsl._

implicit val system = ActorSystem("MyActorSystem")
implicit val materializer = Materializer(system)

val source = Source(1 to 10)
val sink = Sink.foreach[Int](println)
val flow = Flow[Int].map(_ * 2)

// Connecting the source, flow, and sink
val runnableGraph = source.via(flow).to(sink)

// Running the stream
runnableGraph.run()

// Shutting down the system
system.terminate()