STM和原子地:为什么这两个程序的语义不同?

时间:2014-10-16 08:09:29

标签: multithreading haskell concurrency stm

让我们考虑一下这个简单的Haskell程序:

module Main where

import Control.Concurrent.STM
import Control.Concurrent
import Control.Exception
import Control.Monad
import Data.Maybe
import Data.Monoid
import Control.Applicative


terminator :: Either SomeException () -> IO ()
terminator r = print $ "Dying with " <> show r

doStuff :: TMVar () -> TChan () -> Int -> IO ()
doStuff writeToken barrier w = void $ flip forkFinally terminator $ do
  hasWriteToken <- isJust <$> atomically (tryTakeTMVar writeToken)
  case hasWriteToken of
    True -> do
      print $ show w <> "I'm the lead.."
      threadDelay (5 * 10^6)
      print "Done heavy work"
      atomically $ writeTChan barrier ()
    False -> do
      print $ show w <> " I'm the worker, waiting for the barrier..."
      myChan <- atomically $ dupTChan barrier
      _ <- atomically $ readTChan myChan
      print "Unlocked!"



main :: IO ()
main = do
  writeToken <- newTMVarIO ()
  barrier <- newBroadcastTChanIO
  _ <- forM [1..20] (doStuff writeToken barrier)
  threadDelay (20 * 10^6)
  return ()
它本质上是一个并发场景的模型,其中“领导”获取写入令牌,做一些事情,工作人员将在屏障和路径上同步“绿灯”。这有效,但是如果我们用这个替换工人“原子”块:

  _ <- atomically $ do
    myChan <- dupTChan barrier
    readTChan myChan

所有工人在STM交易中无限期地被阻止:

"Done heavy work" 
"Dying with Right ()"
"Dying with Left thread blocked indefinitely in an STM transaction"
"Dying with Left thread blocked indefinitely in an STM transaction"
"Dying with Left thread blocked indefinitely in an STM transaction"
...

我怀疑关键在于atomically的语义。任何的想法? 谢谢! 阿尔弗雷

1 个答案:

答案 0 :(得分:2)

我认为这种行为来自dupTChan的定义。为了便于阅读,请在此处复制,以及readTChan

dupTChan :: TChan a -> STM (TChan a)
dupTChan (TChan _read write) = do
  hole <- readTVar write  
  new_read <- newTVar hole
  return (TChan new_read write)

readTChan :: TChan a -> STM a
readTChan (TChan read _write) = do
  listhead <- readTVar read
  head <- readTVar listhead
  case head of
    TNil -> retry
    TCons a tail -> do
    writeTVar read tail
    return a

内联这些函数,我们得到了这个STM块:

worker_block (TChan _read write) = do
  hole <- readTVar write
  new_read <- newTVar hole
  listhead <- readTVar new_read
  head <- readTVar listhead
  case head of
    TNil -> retry
    ...

当您尝试以原子方式运行此块时,我们从通道的尾部创建一个新的read_end,然后在其上调用readTVar。尾部当然是空的,因此readTVar将重试。但是,当线索写入通道时,写入通道的行为会使此事务无效!因此,每个关注者交易都必须重试。

事实上,我不认为dupTChan >>= readTChan除了在STM事务上无限期地阻塞线程之外还会导致其他任何事情。您也可以从文档中解释出来。 dupTChan开始为空,因此在单个原子事务中,除非相同的事务添加它们,否则它将永远不会有任何项目。