Haskell: update two or more tvars atomically probably?

Can a transaction update two different tvars atomically? That is, can I combine data structures from many TV dramas to reduce contention? If so, can you provide an example?

Solution

Yes, you can update multiple tvars atomically in one transaction This is the focus of STM If you can't, it's useless

This is a (silly) example of storing tvars in a data structure It simulates a bunch of random concurrent transactions between bank accounts, where each account is just a TVAR integer Account tvars is saved in the map of account ID, and the account ID itself is saved in TVAR so that new accounts can be created immediately

import Control.Concurrent
import Control.Concurrent.MVar
import Control.Concurrent.STM
import Control.Monad
import System.Random

import qualified Data.Map as Map

type AccountId = Int
type Account = TVar Dollars
type Dollars = Integer
type Bank = TVar (Map.Map AccountId Account)

numberOfAccounts = 20
threads = 100
transactionsPerThread = 100
maxAmount = 1000

-- Get account by ID,create new empty account if it didn't exist
getAccount :: Bank -> AccountId -> STM Account
getAccount bank accountId = do
  accounts <- readTVar bank
  case Map.lookup accountId accounts of
    Just account -> return account
    Nothing -> do
      account <- newTVar 0
      writeTVar bank $Map.insert accountId account accounts
      return account

-- Transfer amount between two accounts (accounts can go negative)
transfer :: Dollars -> Account -> Account -> STM ()
transfer amount from to = when (from /= to) $do
  balanceFrom <- readTVar from
  balanceTo <- readTVar to
  writeTVar from $! balanceFrom - amount
  writeTVar to $! balanceTo + amount

randomTransaction :: Bank -> IO ()
randomTransaction bank = do
  -- Make a random transaction
  fromId <- randomRIO (1,numberOfAccounts)
  toId   <- randomRIO (1,numberOfAccounts)
  amount <- randomRIO (1,maxAmount)

  -- Perform it atomically
  atomically $do
    from <- getAccount bank fromId
    to   <- getAccount bank toId
    transfer amount from to

main = do
  bank <- newTVarIO Map.empty

  -- Start some worker threads to each do a number of random transactions
  workers <- replicateM threads $do
    done <- newEmptyMVar
    forkIO $do
      replicateM_ transactionsPerThread $randomTransaction bank
      putMVar done ()
    return done

  -- Wait for worker threads to finish
  mapM_ takeMVar workers

  -- Print list of accounts and total bank balance (which should be zero)
  summary <- atomically $do
    accounts <- readTVar bank
    forM (Map.assocs accounts) $\(accountId,account) -> do
      balance <- readTVar account
      return (accountId,balance)

  mapM_ print summary
  putStrLn "----------------"
  putStrLn $"TOTAL BALANCE: " ++ show (sum $map snd summary)

If there is no competition in the transfer process, this should print a total balance of zero at the end

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>