STM (Software Transactional Memory)
Software Transactional Memory (STM) is a powerful concurrency primitive that allows you to combine multiple atomic operations into a single transaction.
While Ref is perfect for updating a single variable, STM is designed for scenarios where you need to update multiple variables consistently. FsFlow ensures that the entire transaction is executed atomically—either all changes are committed, or none are.
Note:
STMis currently available on .NET only.
Core Concepts
TRef<'T>: A transactional reference. Similar toRef<'T>, but designed to be used inside an STM transaction.stm { ... }: A computation expression used to compose transactional operations.STM.atomically: The bridge that executes anstmblock as a single atomic effect within aflow.
Basic Usage
Defining a Transaction
Transactions are defined using the stm {} builder. You can read, write, and update multiple TRef values within a single block.
let transferFunds (fromAcc: TRef<decimal>) (toAcc: TRef<decimal>) (amount: decimal) =
stm {
let! currentFrom = TRef.get fromAcc
do! TRef.set (currentFrom - amount) fromAcc
do! TRef.update (fun balance -> balance + amount) toAcc
}
Running the Transaction
To execute an stm block, you use STM.atomically. This turns the transaction into a standard Flow.
let processTransfer fromAcc toAcc amount =
flow {
// Run the transaction atomically
do! STM.atomically (transferFunds fromAcc toAcc amount)
printfn "Transfer complete!"
}
Composition
STM transactions are first-class values. You can compose multiple small transactions into a larger one using stm {} before ever calling atomically. This is the “Software” in STM—it allows for modular, composable concurrency.
let deposit (acc: TRef<decimal>) (amount: decimal) =
TRef.update (fun x -> x + amount) acc
let batchTransfer acc1 acc2 acc3 amount =
stm {
// Composite transaction
do! transferFunds acc1 acc2 amount
do! deposit acc3 (amount / 2.0m)
}
|> STM.atomically
Why use STM instead of Lock?
Using lock manually is error-prone and can lead to deadlocks if different parts of your code acquire multiple locks in a different order. STM eliminates deadlocks by managing a consistent commit order internally and makes complex state transitions much easier to write and maintain.
API Reference: Module TRef
| Function | Signature | Description |
|---|---|---|
make |
'T -> STM<TRef<'T>> |
Creates a new transactional reference. |
get |
TRef<'T> -> STM<'T> |
Reads the value of a TRef within a transaction. |
set |
'T -> TRef<'T> -> STM<unit> |
Sets the value of a TRef within a transaction. |
update |
('T -> 'T) -> TRef<'T> -> STM<unit> |
Atomically updates a TRef within a transaction. |
API Reference: Module STM
| Function | Signature | Description |
|---|---|---|
atomically |
STM<'T> -> Flow<'env, 'none, 'T> |
Executes a composed transaction as an atomic flow. |