Stateful Goroutines in Scala

Our example demonstrates how to manage state using actors in Scala, which is similar to the concept of goroutines in other languages. This approach aligns with Scala’s ideas of sharing memory by communicating and having each piece of data owned by exactly one actor.

import scala.actors.Actor
import scala.actors.Actor._
import scala.util.Random
import java.util.concurrent.atomic.AtomicLong
import scala.concurrent.duration._

// In this example our state will be owned by a single actor.
// This will guarantee that the data is never corrupted with
// concurrent access. In order to read or write that state,
// other actors will send messages to the owning actor and
// receive corresponding replies.

case class ReadOp(key: Int, replyTo: Actor)
case class WriteOp(key: Int, value: Int, replyTo: Actor)

class StateManager extends Actor {
  private var state = Map[Int, Int]()

  def act() {
    loop {
      react {
        case ReadOp(key, replyTo) =>
          replyTo ! state.getOrElse(key, 0)
        case WriteOp(key, value, replyTo) =>
          state += (key -> value)
          replyTo ! true
      }
    }
  }
}

object StatefulActors {
  def main(args: Array[String]): Unit = {
    // As before we'll count how many operations we perform.
    val readOps = new AtomicLong(0)
    val writeOps = new AtomicLong(0)

    // Create the state manager actor
    val stateManager = new StateManager
    stateManager.start()

    // This starts 100 actors to issue reads to the
    // state-owning actor via messages.
    for (_ <- 0 until 100) {
      actor {
        loop {
          stateManager ! ReadOp(Random.nextInt(5), self)
          self.receive { case _ => }
          readOps.incrementAndGet()
          Thread.sleep(1)
        }
      }
    }

    // We start 10 writes as well, using a similar approach.
    for (_ <- 0 until 10) {
      actor {
        loop {
          stateManager ! WriteOp(Random.nextInt(5), Random.nextInt(100), self)
          self.receive { case _ => }
          writeOps.incrementAndGet()
          Thread.sleep(1)
        }
      }
    }

    // Let the actors work for a second.
    Thread.sleep(1000)

    // Finally, capture and report the op counts.
    println(s"readOps: ${readOps.get()}")
    println(s"writeOps: ${writeOps.get()}")
  }
}

Running our program shows that the actor-based state management example completes a large number of total operations:

$ scala StatefulActors.scala
readOps: 71708
writeOps: 7177

For this particular case, the actor-based approach in Scala is similar in complexity to the goroutine-based one in other languages. It might be useful in certain cases, for example where you have other message-passing scenarios involved or when managing multiple such shared states would be error-prone. You should use whichever approach feels most natural, especially with respect to understanding the correctness of your program.

In Scala, actors provide a way to handle concurrency that’s conceptually similar to goroutines and channels in other languages. They allow for message-passing concurrency, which can be a powerful tool for managing shared state and avoiding race conditions.