We didn't get to these in class because of talking about mt-interpreter instead. We can talk
about these during the exam review next discussion. If you have questions, please bring 
them to discussion.

example.ml:
A simple program in mt-interpreter with a data race, and then a fixed version.

Run the programs e1 and then e2 with mt-interpreter.ml.
e1 with and without the skip line produces different outputs, which
demonstrates how thread scheduling/interleaving of the threads leads
to a data race. 

Why does e1 have a data race and why does e2 fix it?
Basically T2's reading x and updating x to x+1 need to be together in a critical section.
In e1, having the different l and m locks aren't enforcing that. x may get updated to 3 by the
other thread after the read but before the update to x+1.
x=3 is followed by x=1 even though x has already been updated to 3 (should become x=4).

e2 changes to acquiring lock l instead of m.
You can still get different outputs based on different thread schedules, but this is because of
the ordering of critical sections (which is okay/expected), rather than interleaving within the 
critical section (as in e1). In other words, once x=3, you may get x=4 afterwards, but there's no
possibility for x=1 after x has been updated to 3.

An analogous situation (you don't need to know the database context but this is just an
example application that might help clarify the difference):
Consider two bank transactions occurring in a database.
T1 does balance = 100 (like x = 3 in e1)
T2 reads balance, and then updates it to balance = balance + 50 (like read x, x = x + 1 in e1)
The database considers these as possible correct outputs after both threads have completed:
balance = 100 from the order T2, T1 (like x = 3)
OR
balance = 150 from the order T1, T2 (like x = 4)
However, balance = 50 is incorrect (like x = 1 in e1, which cannot happen in e2)
   Optional knowledge: this inconsistency is known as the "lost update phenomenon"
   where T1 writes to x between T2's read x and write to x (so T1's update gets lost).

prodcons.zip:
A producer-consumer example in Java. Compare to p4 and p5 in mt-interpreter.ml.
Notice the use of competitive (locks) and cooperative (wait/notify) synchronization.
Side note: the methods notify (one waiting thread) vs. notifyAll (all waiting threads) in Java
