One of the major advantages of software transactional memory that always gets mentioned is composability and modularity. Different fragments can be combined to produce larger components. In lock-based programs, this is often not the case.
I am looking for a simple example illustrating this with actual code. I'd prefer an example in Clojure, but Haskell is fine too. Bonus points if the example also exhibits some lock-based code which can't be composed easily.
A translation of Ptival's example to Clojure:
So
debit
andcredit
are fine to call on their own, and like the Haskell version, the transactions nest, so the wholetransfer
operation is atomic, retries will happen on commit collisions, you could add validators for consistency, etc.And of course, semantics are such that they will never deadlock.
An example where locks don't compose in Java:
So, deposit is okay, withdraw is okay, but transfer is not okay: if A begins a transfer to B when B begins a transfer to A, we can have a deadlock situation.
Now in Haskell STM:
Since there is no explicit lock,
withdraw
anddeposit
compose naturally intransfer
. The semantics still ensure that if withdraw fails, the whole transfer fails. It also ensures that the withdraw and the deposit will be done atomically, since the type system ensures that you cannot call transfer outside of anatomically
.This example comes from this: http://cseweb.ucsd.edu/classes/wi11/cse230/static/lec-stm-2x2.pdf Adapted from this paper you might want to read: http://research.microsoft.com/pubs/74063/beautiful.pdf
And to make trprcolin's example even more idiomatic i would suggest changing parameter order in transact function, and make definitions of debit and credit more compact.
Here's a Clojure example:
Suppose you have a vector of bank accounts (in real life the vector may be somewhat longer....):
And a "transfer" function that safely transfers an amount between two accounts in a single transaction:
Which works as follows:
You can then easily compose the transfer function to create a higher level transaction, for example transferring from multiple accounts:
Note that all of the multiple transfers happened in a single, combined transaction, i.e. it was possible to "compose" the smaller transactions.
To do this with locks would get complicated very quickly: assuming the accounts needed to be individually locked then you'd need to do something like establishing a protocol on lock acquisition order in order to avoid deadlocks. As Jon rightly points out you can do this in some cases by sorting all the locks in the system, but in most complex systems this isn't feasible. It's very easy to make a hard-to-detect mistake. STM saves you from all this pain.