IORef
、MVar
和TVar
可以用来在并发上下文中包装共享变量。我已经学习了一段时间的并发Haskell,现在遇到了一些问题。在stackoverflow上搜索并阅读了一些相关问题后,我的问题并没有完全解决。
- 根据
IORef
的文档,“将原子性扩展到多个IORef存在问题”,有人能帮忙解释为什么单个IORef
是安全的,但多个IORef
存在问题吗? modifyMVar
是“异常安全的,但只有在没有其他生产者时才是原子的”。请参见MVar
的文档。源代码显示,modifyMVar
仅按顺序组合了getMVar
和putMVar
,这表明如果有另一个生产者,则它不是线程安全的。但如果没有生产者并且所有线程都按“takeMVar
然后putMVar
”的方式行事,那么仅使用modifyMVar
是否是线程安全的?
为了给出一个具体的情况,我将展示实际问题。我有一些从不为空的共享变量,并希望它们成为可变状态,以便一些线程可以同时修改这些变量。
好的,看起来TVar
清楚地解决了一切。但我对此不满意,渴望得到上述问题的答案。任何帮助都将不胜感激。
-------------- re: @GabrielGonzalez BFS接口代码 ------------------
下面的代码是我使用状态单子实现的BFS接口。
{-# LANGUAGE TypeFamilies, FlexibleContexts #-}
module Data.Graph.Par.Class where
import Data.Ix
import Data.Monoid
import Control.Concurrent
import Control.Concurrent.MVar
import Control.Monad
import Control.Monad.Trans.State
class (Ix (Vertex g), Ord (Edge g), Ord (Path g)) => ParGraph g where
type Vertex g :: *
type Edge g :: *
-- type Path g :: * -- useless
type VertexProperty g :: *
type EdgeProperty g :: *
edges :: g a -> IO [Edge g]
vertexes :: g a -> IO [Vertex g]
adjacencies :: g a -> Vertex g -> IO [Vertex g]
vertexProperty :: Vertex g -> g a -> IO (VertexProperty g)
edgeProperty :: Edge g -> g a -> IO (EdgeProperty g)
atomicModifyVertexProperty :: (VertexProperty g -> IO (VertexProperty g)) ->
Vertex g -> g a -> IO (g a) -- fixed
spanForest :: ParGraph g => [Vertex g] -> StateT (g a) IO ()
spanForest roots = parallelise (map spanTree roots) -- parallel version
spanForestSeq :: ParGraph g => [Vertex g] -> StateT (g a) IO ()
spanForestSeq roots = forM_ roots spanTree -- sequencial version
spanTree :: ParGraph g => Vertex g -> StateT (g a) IO ()
spanTree root = spanTreeOneStep root >>= \res -> case res of
[] -> return ()
adjs -> spanForestSeq adjs
spanTreeOneStep :: ParGraph g => Vertex g -> StateT (g a) IO [Vertex g]
spanTreeOneStep v = StateT $ \g -> adjacencies g v >>= \adjs -> return (adjs, g)
parallelise :: (ParGraph g, Monoid b) => [StateT (g a) IO b] -> StateT (g a) IO b
parallelise [] = return mempty
parallelise ss = syncGraphOp $ map forkGraphOp ss
forkGraphOp :: (ParGraph g, Monoid b) => StateT (g a) IO b -> StateT (g a) IO (MVar b)
forkGraphOp t = do
s <- get
mv <- mapStateT (forkHelper s) t
return mv
where
forkHelper s x = do
mv <- newEmptyMVar
forkIO $ x >>= \(b, s) -> putMVar mv b
return (mv, s)
syncGraphOp :: (ParGraph g, Monoid b) => [StateT (g a) IO (MVar b)] -> StateT (g a) IO b
syncGraphOp [] = return mempty
syncGraphOp ss = collectMVars ss >>= waitResults
where
collectMVars [] = return []
collectMVars (x:xs) = do
mvx <- x
mvxs <- collectMVars xs
return (mvx:mvxs)
waitResults mvs = StateT $ \g -> forM mvs takeMVar >>= \res -> return ((mconcat res), g)
TVar
?stm
是Haskell中真正优雅的并发解决方案。我从未遇到过无法使用软件事务内存解决的问题。 - Gabriella GonzalezmodifyMVar
的行为时,“线程安全”是否是合适的词语;你的程序不会像Python那样崩溃或爆炸,而只会在你的线程中抛出“无限期阻塞”的异常。 - jberrymanMVar
的行为方式。像modifyMVar
一样调用takeMVar
将使MVar
保持为空,直到调用putMVar
。你的示例将导致线程 B 在MVar
上阻塞,直到线程 A 完成其putMVar
操作,并且将导致线程 B 读取线程 A 放入MVar
中的值。在你的示例中,除非其他线程向MVar
中放入某些内容,否则 B 无法继续进行,直到 A 完成。 - sabaumaatomicModifyIORef
应该是可以的。但是,如果您想将多个修改组合成单个原子修改,则需要使用STM。 - Gabriella Gonzalez