Transactions

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 Seq. The randomAccount function uses uniformR to generate a random list index, then uses Seq.index to grab the account at that position.

Next we start many threads to create frantic account activity.

Each thread has its own random number generator.

These threads run in an infinite loop; they will stop only when the program ends.

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.

Now we come to the interesting transaction.

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.

Then we print the list of balances and their sum total.

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.

Next: