Haskell: Обновление двух или более TVars атомарно. Возможный?
Может ли одна транзакция обновить две разные TVar
с атомным способом? т.е. я могу составить структуры данных из множества TVar
s, чтобы уменьшить раздор? Если да, не могли бы вы привести пример?
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.