如何在Haskell中使用线程安全的共享变量

pys*_*ing 7 concurrency haskell thread-safety atomicity

IORefs,MVars和TVars可用于在并发上下文中包装共享变量.我已经研究了并发haskell一段时间了,现在我已经解决了一些问题.在stackoverflow上搜索并阅读了一些相关问题后,我的问题没有完全解决.

  1. 根据IORef 文档,"将原子性扩展到多个IORefs是有问题的",有人可以帮助解释为什么单个IORef是安全的但不止一个IORef是有问题的吗?
  2. modifyMVar是"异常安全,但只有原子,如果没有其他生产者为这个MVar".查看MVar文档.源代码显示modifyMVar只组成a getMVarputMVar顺序,表明如果有另一个生产者,它是注意线程安全的.但是如果没有生产者并且所有线程都以" takeMVar然后putMVar"的方式运行,那么简单地使用它是否是线程安全的modifyMVar

为了给出具体情况,我将展示实际问题.我有一些永远不会为空的共享变量,我希望它们是可变状态,因此一些线程可以同时修改这些变量.

好吧,似乎可以TVar清楚地解决所有问题.但我对此并不满意,我渴望得到上述问题的答案.任何帮助表示赞赏.

-------------- re:@GabrielGonzalez BFS界面代码------------------

下面的代码是我使用状态monad的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)
Run Code Online (Sandbox Code Playgroud)

Dan*_*ner 5

  1. 现代处理器提供比较和交换指令,以原子方式修改单个指针.我希望如果你追踪得足够深,你会发现这条指令是用来实现的atomicModifyIORef.因此,很容易为单个指针提供原子访问.但是,由于没有对多个指针的硬件支持,无论您需要什么都必须在软件中完成.这通常涉及在所有线程中发明并手动执行协议 - 这很复杂且容易出错.

  2. 是的,如果所有线程同意仅使用"单一takeMVar后跟单一putMVar"行为,则modifyMVar是安全的.