IORef
s, MVar
s e TVar
s può essere utilizzato per avvolgere una variabile condivisa in un contesto concorrente. Ho studiato haskell concomitante per un po 'e ora ho riscontrato alcune domande. Dopo aver cercato su StackOverflow e aver letto alcune domande correlate, le mie domande non sono completamente risolte.Come utilizzare variabili condivise thread-safe in Haskell
- Secondo il
IORef
documentation, "Estrazione del atomicità a più IORefs è problematico", qualcuno può aiutare a spiegare il motivo per cui un singoloIORef
è sicuro, ma più di unIORef
s sono problematici? modifyMVar
è "eccezionalmente sicuro, ma solo atomico se non ci sono altri produttori per questo MVar". VediMVar
's documentation. Il codice sorgente mostra chemodifyMVar
compone solo ungetMVar
eputMVar
in sequenza, a indicare che è nota la sicurezza del thread se c'è un altro produttore. Ma se non c'è nessun produttore e tutti i thread si comportano nel modo "takeMVar
thenputMVar
", allora è thread-safe usare semplicementemodifyMVar
?
Per dare una situazione concreta, mostrerò il problema reale. Ho alcune variabili condivise che non sono mai vuote e voglio che siano stati mutabili, quindi alcuni thread possono modificare contemporaneamente queste variabili.
OK, sembra che lo TVar
risolva tutto chiaramente. Ma non sono soddisfatto e sono desideroso di risposte alle domande sopra. Qualsiasi aiuto è apprezzato.
-------------- Re: @GabrielGonzalez BFS codice di interfaccia ------------------
codice sotto è la mia interfaccia BFS usando la monade di stato.
{-# 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)
Perché non sei soddisfatto di 'TVar's? 'stm' è la soluzione elegante di Haskell per la concorrenza. Non ho mai incontrato un problema che non potessi risolvere utilizzando la memoria transazionale del software. –
Non sono sicuro che "thread-safety" sia la parola appropriata nel parlare del comportamento di 'modifyMVar'; il tuo programma non segnerà o esploderà come in Python, ma otterrai solo eccezioni "bloccate indefinitamente" generate nei tuoi thread. – jberryman
@GabrielGonzalez Immagino che l'implementazione di STM usando "log then commit" sia relativamente inefficiente e possa causare troppo overhead. Ma non ho paragonato l'efficienza quantitativa, però. – pysuxing