IORef
年代,MVar
s 和 TVar
s 可用于在并发上下文中包装共享变量。我已经研究了并发 haskell 一段时间,现在我遇到了一些问题。在stackoverflow上搜索并阅读了一些相关问题后,我的问题并没有完全解决。
IORef
documentation “将原子性扩展到多个 IORef 是有问题的”,有人可以帮助解释为什么单个 IORef
是安全的,但不止一个 IORef
s有问题吗? modifyMVar
是“异常安全的,但只有在此 MVar 没有其他生产者时才具有原子性”。见 MVar
的documentation .源代码显示modifyMVar
仅构成 getMVar
和 putMVar
顺序地,表明如果有另一个生产者,它是线程安全的。但是如果没有生产者并且所有线程都以“takeMVar
然后 putMVar
”的方式运行,那么简单地使用 modifyMVar
是线程安全的吗? ? 为了给出具体情况,我将展示实际问题。我有一些永远不会为空的共享变量,我希望它们是可变状态,因此一些线程可以同时修改这些变量。
好的,看来是
TVar
清楚地解决一切。但我对此并不满意,我渴望得到上述问题的答案。任何帮助表示赞赏。-------------- 回复:@GabrielGonzalez BFS 接口(interface)代码 ------
下面的代码是我使用状态单子(monad)的 BFS 接口(interface)。
{-# 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)
最佳答案
atomicModifyIORef
的指令。 .因此很容易提供对单个指针的原子访问。但是,由于没有对多个指针的此类硬件支持,因此无论您需要什么,都必须在软件中完成。这通常涉及在所有线程中发明和手动执行协议(protocol)——这很复杂且容易出错。 takeMVar
后跟单个 putMVar
”行为,那么 modifyMVar
是安全的。 关于haskell - 如何在 Haskell 中使用线程安全的共享变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15857808/