【问题标题】:Haskell: Updating two or more TVars atomically. Possible?Haskell:自动更新两个或多个 TVar。可能的?
【发布时间】:2012-04-11 04:29:14
【问题描述】:

一个事务能否以原子方式更新两个不同的TVars?即我可以用大量TVars 组成数据结构以减少争用吗?如果有,你能举个例子吗?

【问题讨论】:

    标签: haskell concurrency stm tvar


    【解决方案1】:

    一个事务能否以原子方式更新两个不同的 TVar?

    是的,您可以在一个事务中自动更新多个 TVar。这就是STM的全部意义所在。如果你不能,它就不会很有用。

    我可以从大量 TVar 中组合数据结构以减少争用吗?如果有,能举个例子吗?

    这是一个将 TVar 存储在数据结构中的(有点傻的)示例。它模拟了银行账户之间的一堆随机并发交易,其中每个账户只是一个TVar Integer。帐户 TVar 保存在帐户 ID 的映射中,帐户 ID 本身保存在 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)
    

    如果在转移期间没有竞争条件,这应该在最后打印总余额为零。

    【讨论】:

    • 不错的答案。我有一个问题。可以将atomically 块从randomTransaction 移动到transfer 吗?我的意思是从randomTransaction 中删除atomically 并用atomically 包裹transfer 的整个主体。
    • 完美答案之一!谢谢@hammar。
    【解决方案2】:

    事务是完全原子的;如果它修改了多个TVars,则这两个更改将同时发生,原子地,孤立地发生。在单个 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 将自动交换两个 TVars。以这种方式实现原子事务的可组合性是 STM 的主要优点之一。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-28
      • 1970-01-01
      相关资源
      最近更新 更多