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

标签 haskell concurrency thread-safety atomic

IORef年代,MVar s 和 TVar s 可用于在并发上下文中包装共享变量。我已经研究了并发 haskell 一段时间,现在我遇到了一些问题。在stackoverflow上搜索并阅读了一些相关问题后,我的问题并没有完全解决。

  • 根据 IORef documentation “将原子性扩展到多个 IORef 是有问题的”,有人可以帮助解释为什么单个 IORef是安全的,但不止一个 IORef s有问题吗?
  • modifyMVar是“异常安全的,但只有在此 MVar 没有其他生产者时才具有原子性”。见 MVardocumentation .源代码显示modifyMVar仅构成 getMVarputMVar顺序地,表明如果有另一个生产者,它是线程安全的。但是如果没有生产者并且所有线程都以“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/

    相关文章:

    haskell - 美丽而可怕的 haskell

    java - 具有最快(并发)添加操作的集合

    .net - 数据表和线程安全

    java - 在 Java Web 爬虫中实现线程

    java - ExecutorService 异常和内存泄漏?

    concurrency - Golang 并发数组访问

    java - 在继续之前强制方法完成

    events - 将交互添加到场景图中(在 Haskell 中)

    haskell - 封闭括号内的美元符号

    haskell - Haskell 中的表达式评估 : Fixing the type of a sub-expression causes parent expression to be evaluated to different degrees