The Perils of Concurrency [this content is outdated with the advent of Rust]
Jaideep Ganguly
Concurrency Managing concurrency is wickedly tricky for large programs. At each program point, you must reason about which locks are currently held. At each method call, you must reason about which locks it will try to hold, and convince yourself that it does not overlap the set of locks already held.
A Diverging Problem This problem is magnified because the locks you reason about are not simply a finite list in the program since the program is free to create new locks at run time as it executes. This gets worse because testing is not reliable with locks. Threads are non‐ deterministic, you can very well successfully test a program a thousand times and yet the program could go wrong the first time it runs on deployment! With locks, you must get the program correct through logic alone and that is very hard and time consuming to do so.
Will over Engineering work? Over engineering will not solve the problem. Just as you avoid locks, you cannot also put a lock around every operation. The problem is that while new lock operations remove possibilities for race conditions, itsimultaneously introduces possibilities for deadlocks. A correct lock‐using program must have neither of these. Net net, there is no good way to fix locks and make them practical to use.
Actors and Threads That is why Scala provides an alternative concurrency approach, one based on Erlang's message‐passing actors. An actor is a kind of thread that has a mailbox for receiving messages. Actors are implemented on top of normal Java threads. Java threads are not cheap. Typical Java virtual machines can have millions of objects but only a few thousand of threads. And remember, switching threads can often take thousands of processor cycles. For a program to be efficient, it is important to be sparing with thread creation and thread switching.
Example Code Below is a simple example of message passing with Actors that leverages Scala Case Classes.
x1import actors._
2
3object Ex_Actor {
4 case object Quit
5 case class Message(s: String)
6 case class Number(n: Int)
7
8 /* Simple Actor receiving messages as case classes */
9 class SimpleActor extends Actor {
10
11 def act(): Unit = {
12 var flag = true
13 while (flag) {
14 receive {
15 case Quit =>
16 flag = false
17 case Message(s) =>
18 println("Got a string: " + s)
19 case Number(i) =>
20 println("Got a number: " + i)
21 }
22 }
23 }
24 }
25
26 case class StartCount(n: Int, a: CountActor)
27 case class CountDown(n: Int)
28 class CountActor(name: String) extends Actor { /* CountActor */
29
30 def act(): Unit = {
31 var flag = true
32 while (flag) {
33 receive {
34 case StartCount(n, ca) =>
35 println(name + " : " + n)
36 ca ! CountDown(n - 1)
37
38 case CountDown(n) =>
39 println(n)
40 if (n > 0) {
41 sender ! CountDown(n - 1)
42 println(name)
43 }
44
45 case Quit => flag = false
46
47 }
48
49 }
50 }
51 }
52
53def main(args: Array[String]): Unit = {
54
55 val ca1 = new CountActor("Counting Actor 1")
56 val ca2 = new CountActor("Counting Actor 2")
57
58 ca1.start()
59 ca2.start()
60
61 ca2 ! StartCount(10, ca1)
62}