让我们考虑一下这个简单的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
的语义。任何的想法?
谢谢!
阿尔弗雷
答案 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
开始为空,因此在单个原子事务中,除非相同的事务添加它们,否则它将永远不会有任何项目。