I’m new to Scala, and I want to write some multi-threaded code with pattern matching, and I was wondering if I could treat the pattern-matching code as atomic.
For example:
abstract class MyPoint
case class OneDim(x : Int) extends MyPoint
case class TwoDim(x : Int, y : Int) extends MyPoint
var global_point : MyPoint = new OneDim(7)
spawn {
Thread.sleep(scala.util.Random.nextInt(100))
global_point = new TwoDim(3, 9)
}
Thread.sleep(scala.util.Random.nextInt(100))
match global_point {
case TwoDim(_, _) => println("Two Dim")
case OneDim(_) => println("One Dim")
}
Is it possible that the execution will go as the following:
- The main thread reaches the “match global_point” code, finds out that *global_point* is not of type TwoDim and pauses (returns to the scheduler).
- The spawned thread changes *global_point* to be of type TwoDim
- The main thread returns, finds out that the *global_point* is not of type OneDim, thinks there are no matches to *global_point* and raises a NoMatch exception.
Is this kind of execution avoided internally by Scala? If it does, then how? Does the matching take a snapshot of the object and then try to match it against the patterns? Is there a limit to the snapshot depth (the match patterns can be complex and nested)?
This is not hard evidence from the specs, but it illustrates some things the compiler does for you, and that should allow you to regard a few match blocks as atomic – but definitely not all. It will be much safer if you synchronise your code yourself, or if you go with immutable objects.
Flat example
If you run the following script with
scala -print:you’ll see the desugared intermediate code created by the compiler (I’ve removed some code for brevity):
The possibly shared object referenced by
mgets a local aliastemp1, so ifmis changed in the background such that it points to another object, then the match still takes place on the old objectmpointed to. Hence, the situation you described above (changingglobal_pointto point to aTwoDiminstead of to aOneDim) is not a problem.Nested example
It seems to generally be the case that the compiler creates local aliases to all objects that are bound in the guard of a match case, but it does not create a deep copy!
For the following script:
the compiler creates this intermediate code:
Here, the compiler creates local aliases for the object
xyou match on, and for its two sub-objectsx.f(bound tof) andx.x(bound toix), but not forix.f. Hence, if the structure you match on is deeply nested and your cases depend on nested objects that you don’t bind locally, then race conditions can occur. And will, as we all know, due to Murphy’s Law.