我想在两个线程之间实现一个管道。我有线程A获取数据,处理它,并将其发送到线程B.我有一个MVar,检查数据是否完全处理
但是,我有例外*** Exception: thread blocked indefinitely in an STM transaction
为什么我的线程被阻止了?我虽然比第一个线程在通道上写入时,然后当通道上有数据时,第二个可以读取它
fstPipe :: (a -> b) -> TChan b -> MVar () -> [a] -> IO ()
fstPipe f chIn m xs = do
( mapM_(\x-> atomically $ writeTChan chIn $ f x) xs) >> putMVar m ()
pipelineDone channel mIn = do
isDone <- fmap isJust $ tryTakeMVar mIn
isEmpty <- atomically $ isEmptyTChan channel
return $ isDone && isEmpty
lastPipe f chIn mIn = iter
where iter = do
atomically $ fmap f $ readTChan chIn
isDone <- pipelineDone chIn mIn
unless isDone $ iter
pipeline = do
chIn <- atomically newTChan
m <- newEmptyMVar
first <- async $ fstPipe reverse chIn m $ replicate 10 [1..500]
last <- async $ lastPipe print chIn m
wait first
wait last
答案 0 :(得分:3)
在同一代码块中使用STM 和信号量对我来说似乎很奇怪...为什么不在STM中执行整个操作?
特别是,为什么不TChan (Maybe x)
,Nothing
表示序列的结束?
另外,请注意,您的fstPipe
可能只会生成一堆未评估的thunk并立即将它们放入TChan
,而不会实际计算任何内容。您可能希望seq
或类似内容强制某个实际的工作在该线程上发生。
答案 1 :(得分:2)
我认为存在竞争条件:
fstPipe
putMVar
lastPipe
阅读所有内容,然后致电pipelineDone
pipelineDone
返回False
,因为putMVar
尚未完成lastPipe
会尝试阅读频道putMVar
执行,但为时已晚现在lastPipe
在空白频道上停留阅读。
答案 2 :(得分:1)
您的问题符合pipelineDone
的逻辑。目前,您有:
pipelineDone channel mIn = do
isDone <- fmap isJust $ tryTakeMVar mIn
isEmpty <- atomically $ isEmptyTChan channel
return $ isDone && isEmpty
tryTakeMVar
将采用MVar的内容,假设其中有某些内容。假设您的制作人首先完成,它将把()
写入MVar。然后,您的消费者将尝试获取其内容。如果成功,则MVar变空。任何后续tryTakeMVar
将始终返回Nothing
,因此isDone && isEmpty
将始终返回false,您将继续尝试从TChan
读取。一旦TChan
变空,GHC就可以告诉你它遇到了死锁。
您应该将pipelineDone实现更改为:
pipelineDone channel mIn = do
stillRunning <- isEmptyMVar mIn
isEmpty <- atomically $ isEmptyTChan channel
return $ (not stillRunning) && isEmpty
这将简单地轮询MVar,而不是实际清空它。