Each block of code inside the
atomically function is a transaction. When you read and write
TVars within a transaction, you are shielded from the effects of any other threads that may also be messing with the variables concurrently.
In celebration of how transactional variables free us from a lot of messy concerns that usually complicate concurrent programming, here is a classic demonstration that simulates passing money around between bank accounts.
These are our standard imports from stm for working with mutable references.
This demonstration will create a bunch of concurrent threads that perform actions at random.
We use the mwc-random library to generate random numbers.
We will store the list of accounts in a sequence to allow efficient lookups by index when selecting an account at random.
First we initialize ten
TVars representing ten account balances.
The standard list type is great for looping over, but for any other access pattern we look to other data structures. Here we’ll want to look up accounts by their position in the list, so we’ll store them in a
randomAccount function uses
uniformR to generate a random list index, then uses
Seq.index to grab the account at that position.
Each thread has its own random number generator.
Between each iteration, a brief pause lasting somewhere between ten and fifty microseconds.
In each loop iteration, one transaction. Two accounts are chosen at random to be the sender and the recipient.
The amount to transfer is also selected at random, a number between one and ten.
First it reduces the sender’s balance by the transfer amount, then checks to make sure that this did not overdraw the account. If the new balance is below zero, the transaction is aborted.
Then it increases the recipient’s balance by the same amount.
The alternative possibility, reached if the first transaction aborts, is to return without doing anything. (If we did not include this branch, then the thread would block until the transaction is able to run successfully.)
The final piece of the program is to make some observations of the program state so we can see what’s happening. We’ll repeat this four times, pausing for half of a second between each sample.
In a single transaction, we read all of the account balances.
Since the transactions are random, you’ll see a different result every time you run this program. But, no matter what:
- No account balance ever drops below zero.
- The total amount of money among the accounts remains at 1,000.