Haskell: Обновление двух или более TVars атомарно. Возможный?

Может ли одна транзакция обновить две разные TVarс атомным способом? т.е. я могу составить структуры данных из множества TVars, чтобы уменьшить раздор? Если да, не могли бы вы привести пример?

2 ответа

Решение

Может ли одна транзакция обновить два разных TVars атомарным способом?

Да, вы можете обновить несколько TVars атомарно в одной транзакции. В этом вся суть СТМ. Это было бы не очень полезно, если бы ты не мог.

Могу ли я составить структуры данных из множества телевизионных программ, чтобы уменьшить конкуренцию? Если да, не могли бы вы привести пример?

Вот (несколько глупый) пример хранения TVars в структуре данных. Он имитирует несколько случайных параллельных транзакций между счетами в банке, где каждый счет является просто TVar Integer, Учетные записи TVars хранятся в карте из идентификаторов учетных записей, которая сама хранится в TVar, поэтому новые учетные записи можно создавать на лету.

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)

Это должно напечатать итоговое сальдо в конце, если не было условий гонки во время передач.

Транзакция полностью атомарна; если он изменяет несколько TVar s, оба изменения будут происходить вместе, атомарно, изолированно. Все работает в одном atomically блок - это одна транзакция Например:

swap :: (Num a) => TVar a -> TVar a -> STM ()
swap v1 v2 = do
    a <- readTVar v1
    b <- readTVar v2
    writeTVar v1 b
    writeTVar v2 a

Вот, swap a b будет атомно поменять местами два TVar s. Таким образом, составляемость атомарных транзакций является одним из основных преимуществ STM.

Другие вопросы по тегам