2013-04-07 2 views
6

s MVar s TVar s는 동시 컨텍스트에서 공유 변수를 래핑하는 데 사용할 수 있습니다. 나는 잠시 동안 동시성 해법을 배웠고, 이제 나는 몇 가지 질문을 겪었다. stackoverflow 검색 및 관련 질문을 통해 읽은 후 내 질문에 완전히 해결되지 않습니다.Haskell에서 스레드 안전 공유 변수를 사용하는 방법

  1. 사람이 하나의 IORef 안전하지만 더 이상의 IORef의 문제가있는 이유를 설명하는 데 도움이 될 수 있습니다, "여러 IORefs에 대한 자성이 문제가 확장"는 IORefdocumentation에 따르면?
  2. modifyMVar은 "예외가 발생하지 않지만이 MVar의 다른 제작자가없는 경우에만 아토믹"입니다. MVardocumentation을 참조하십시오. 소스 코드는 modifyMVargetMVarputMVar만을 순차적으로 구성한다는 것을 보여 주며, 다른 제작자가있는 경우 스레드로부터 안전하다는 것을 나타냅니다. 그러나 생산자가없고 모든 스레드가 "takeMVar then putMVar"이라는 방식으로 동작하는 경우 스레드를 사용하면 스레드가 안전합니까? modifyMVar?

구체적인 상황을 설명하기 위해 실제 문제를 보여 드리겠습니다. 나는 결코 비어 있지 않은 공유 변수를 가지고 있는데, 변수를 변경 가능 상태로 유지하여 일부 스레드가 동시에 이러한 변수를 수정할 수있게하고 싶습니다.

좋아, 그게 전부 TVar 분명히 해결 될 것 같습니다. 그러나 나는 그것에 만족하지 않고 위에 열거 한 질문에 대한 답을 원합니다. 어떤 도움을 주셔서 감사합니다.

는 -------------- 재 : @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) 
+0

왜 'TVar'에 만족하지 않습니까? 'stm'은 Haskell의 동시성에 대한 정말 우아한 솔루션입니다. 소프트웨어 트랜잭션 메모리를 사용하여 해결할 수없는 문제를 본 적이 없습니다. –

+0

"thread-safety"가'modifyMVar'의 동작에 대해 이야기 할 때 적절한 단어인지 확신 할 수 없습니다. 당신의 프로그램은 segfault 나 python처럼 날려 버리지 않을 것입니다. 스레드에서 던져진 예외가 "무기한으로 차단"될 것입니다. – jberryman

+0

@ GabrielGonzalez "log then commit"을 사용하는 STM의 구현은 상대적으로 불안정하고 너무 많은 오버 헤드를 유발할 수 있습니다. 그러나 효율성을 정량적으로 비교하지는 못했습니다. – pysuxing

답변

4
  1. 현대 프로세서는 원자 하나의 포인터를 수정하는 비교 및 ​​스왑 명령을 제공합니다. 충분히 깊숙이 추적하면 명령이 atomicModifyIORef을 구현하는 데 사용된다는 것을 알게 될 것입니다. 따라서 단일 포인터에 대한 원자 액세스를 제공하는 것이 쉽습니다. 그러나 하나 이상의 포인터에 대한 하드웨어 지원이 없기 때문에 필요한 것은 소프트웨어로 수행해야합니다. 이것은 일반적으로 모든 스레드에서 프로토콜을 발명하고 수동으로 시행하는 것을 포함합니다. 이는 복잡하고 오류가 발생하기 쉽습니다.

  2. 예, 모든 스레드가 "takeMVar 단일"다음에 단일 putMVar "동작 만 사용하는 것에 동의하면 modifyMVar은 안전합니다.

+0

답장을 보내 주셔서 감사합니다! 아직 구현 기술을 추적하지 못했습니다. 원 자성이 하드웨어에 의해 제한된다면, 하나 이상의 'IORef'를 사용하고 쓰레드간에 공유한다면 어떻게 될 것인가? – pysuxing

+1

@ "여러 개의 IORef로 원 자성을 확장하는 것은 문제가있다"라는 경고는 예를 들어 총계를 카운터':: IORef Int'에서'b :: IORef Int'로 원자 적으로 이동시키고'a'를 0으로 설정합니다. 다른 스레드가 중간 상태를 전혀 보지 못하게하는 방법은 없습니다 (또는 쉬운 방법이 아닙니다). – jberryman

+0

@jberryman 예를 들어 하나 이상의 'IORef' 연산을 하나의 원자 연산으로 구성하는 것이 불가능하다는 것을 의미합니다. 그러나 멀티 스레딩 컨텍스트에서 복합 IORef를 사용하지 않으면 문제가 발생하지 않을 수 있습니까? – pysuxing