2012-04-11 5 views

Antwort

7

Kann eine Transaktion zwei verschiedene TVars auf atomare Weise aktualisieren?

Ja, Sie können mehrere TVars automatisch in einer Transaktion aktualisieren. Das ist der Sinn von STM. Es wäre nicht sehr nützlich, wenn Sie nicht könnten.

Kann ich Datenstrukturen aus vielen TVars zusammensetzen, um Konflikte zu reduzieren? Wenn ja, könnten Sie ein Beispiel geben?

Hier ist ein (etwas albernes) Beispiel zum Speichern von TVars in einer Datenstruktur. Es simuliert eine Reihe zufälliger gleichzeitiger Transaktionen zwischen Konten in einer Bank, wobei jedes Konto nur eine TVar Integer ist. Die Konto-TV-Programme werden in einer Karte aus Konto-IDs gespeichert, die ihrerseits in einer TVar-Datei gespeichert werden, sodass neue Konten im laufenden Betrieb erstellt werden können.

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) 

Dies sollte eine Gesamtbilanz von Null am Ende drucken, wenn es während der Transfers keine Rennbedingungen sind.

+0

Schöne Antwort. Ich habe eine Frage. Kann der atomare Block von "randomTransaction" nach "transfer" verschoben werden? Was ich meine, ist "atomar" von "randomTransaction" zu entfernen und den ganzen Körper von "transfer" mit "atomically" zu umhüllen. –

6

Eine Transaktion ist vollständig atomar; wenn es mehrere TVar s ändert, werden beide Änderungen gemeinsam, atomar, isoliert geschehen. Alles, was in einem einzigen atomically Block ausgeführt wird, ist eine einzelne Transaktion. Zum Beispiel:

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

Hier swap a b wird atomar zwei TVar s tauschen. Die Zusammensetzbarkeit von atomaren Transaktionen auf diese Weise ist einer der wichtigsten Vorteile von STM.