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

9
IORefMVarTVar可以用来在并发上下文中包装共享变量。我已经学习了一段时间的并发Haskell,现在遇到了一些问题。在stackoverflow上搜索并阅读了一些相关问题后,我的问题并没有完全解决。
  1. 根据IORef文档,“将原子性扩展到多个IORef存在问题”,有人能帮忙解释为什么单个IORef是安全的,但多个IORef存在问题吗?
  2. modifyMVar是“异常安全的,但只有在没有其他生产者时才是原子的”。请参见MVar文档。源代码显示,modifyMVar仅按顺序组合了getMVarputMVar,这表明如果有另一个生产者,则它不是线程安全的。但如果没有生产者并且所有线程都按“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)

为什么你不满意TVarstm是Haskell中真正优雅的并发解决方案。我从未遇到过无法使用软件事务内存解决的问题。 - Gabriella Gonzalez
我不确定在谈论modifyMVar的行为时,“线程安全”是否是合适的词语;你的程序不会像Python那样崩溃或爆炸,而只会在你的线程中抛出“无限期阻塞”的异常。 - jberryman
@GabrielGonzalez 我猜STM使用“先记录再提交”的实现方式相对来说效率较低,可能会导致过多的开销。不过我还没有进行定量的效率比较。 - pysuxing
1
@pysuxing 这不是 MVar 的行为方式。像 modifyMVar 一样调用 takeMVar 将使 MVar 保持为空,直到调用 putMVar。你的示例将导致线程 B 在 MVar 上阻塞,直到线程 A 完成其 putMVar 操作,并且将导致线程 B 读取线程 A 放入 MVar 中的值。在你的示例中,除非其他线程向 MVar 中放入某些内容,否则 B 无法继续进行,直到 A 完成。 - sabauma
1
好的,现在我想我理解了你的用途。您只希望每个VertexProperty可以原子地修改。您不关心能否同时修改多个顶点,因此对于您的问题,atomicModifyIORef应该是可以的。但是,如果您想将多个修改组合成单个原子修改,则需要使用STM。 - Gabriella Gonzalez
显示剩余8条评论
1个回答

6
  1. 现代处理器提供了一种比较和交换指令,可以原子性地修改单个指针。我预计如果你深入追踪,你会发现这条指令是用来实现atomicModifyIORef的。因此,很容易提供对单个指针的原子访问。然而,由于没有多个指针的硬件支持,任何你需要的功能都必须在软件中完成。这通常涉及在所有线程中发明并手动执行协议--这是复杂和容易出错的。

  2. 是的,如果所有线程都同意只使用“单个takeMVar接着是单个putMVar”的行为,那么modifyMVar是安全的。


谢谢你的回答!我还没有追踪到具体的实现技术。如果原子性受硬件限制,那么如果我使用多个IORef并在线程之间共享它们会发生什么? - pysuxing
1
@pysuxing,“将原子性扩展到多个IORef存在问题”这一警告是指在执行类似于将计数器a :: IORef Int的总数原子地移动到b :: IORef Int并将a设置为零等操作时所遇到的困难。没有办法(或者说没有简单的方法?)确保其他线程不会看到任何中间状态。 - jberryman
@jberryman,您的意思是说无法将多个IORef操作组合成一个原子操作。但是在多线程上下文中使用多个IORef而不进行组合不会引起问题,我可以这么说吗? - pysuxing
@pysuxing,没错,这是我所说的更好的表达方式 :) - jberryman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接