Output stream has a detailed and entertaining review of handling concurrency with software transaction memory (STM).
Suppose I have a small bank with only two accounts, and all that can happen at this bank is money can be transferred from one account to the other. A “correct” program…[is] when a single thread is performing the transfers, but when multiple threads are performing the transfers, bad things happen.
Imagine standing in a queue in England before WWII. Everyone would fight and pick their way to the teller in order to get service. Any group approaching a crowd in size would get so wildly out of control even the French and Italians complained about barbaric behavior of the English. During the hard times of war, however, the orderly queue was introduced with much propaganda to prevent inefficient riots and fights over scarce resources.
English orderly queuing behavior evolved into a common rule still present today, enforced by others waiting their turn. What happens if people decide to abandon the rule? The old race conditions, incorrect sync, and deadlock would return, as explained in a presentation by Brian Goetz.
STM, instead of trying to enforce concurrency controls offers an isolation boundary for transactions, like moving transactions from an open teller queue into a private office at a bank. Transactions are behind a closed door instead of subject to interruption and blocking.
STM is an alternative to lock-based synchronization. In essence it places a guard around a specified memory location (in our example we’ve placed it around the “accounts” map). The guard is called a Ref. You can only gain access to the memory location by initiating a transaction with an atomic block. Once inside the atomic block, you can gain access to the data in the memory location through the Ref and make modifications. Modifications are isolated, so changes can only be seen within the scope of the atomic block. When the transaction ends, an attempt is made to update the memory location atomically. However, it may be that another thread has committed a transaction before the attempt. If this is the case, the logic inside of an atomic block is retried until an update can be made.
Of course the boundary also has rules to reduce risk for STM. Once inside, bad things can still happen from dependencies, races and interruptions:
- disallow side-effects
- disallow changes
- use independent business logic