1 / 23

Explicit Concurrent Programming in Haskell

Explicit Concurrent Programming in Haskell . QIAN XI COS597C 10/28/2010. Outline. Recap of IO Monad Thread Primitives Synchronization with Locks Message Passing Channels Software Transactional Memory Transactional Memory with Data Invariants. IO Monad in Haskell. Why Monad?

caraf
Download Presentation

Explicit Concurrent Programming in Haskell

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Explicit Concurrent Programming in Haskell QIAN XI COS597C 10/28/2010

  2. Outline • Recap of IO Monad • Thread Primitives • Synchronization with Locks • Message Passing Channels • Software Transactional Memory • Transactional Memory with Data Invariants

  3. IO Monad in Haskell • Why Monad? • Pure functional language needs determinism. fx = e f 3 ... f 3 = 7 • What is Monad? • an abstract data type: IO a, e.g. IO Int • a container of impure, suspended actions/computations = 7? • How to use Monad? do encloses a sequence of computations: an action, a pattern bounded to the result of an action using <- a set of local definitions introduced using let getChar :: IO Char putChar :: Char -> IO () main :: IO () main = do c <- getChar putCharc

  4. Creating Haskell Threads • forkIO :: IO () -> IO ThreadId effect-ful computation identification of a Haskell thread must be used in an IO monad • Concurrency is “lightweight”: both thread creation and context switching overheads are extremely low. • The parent thread will not automatically wait for the child threads to terminate. • forkOS :: IO () -> IO ThreadId • support certain kinds of foreign calls to external code. Ex: fibEuler.hs

  5. Mutable Variable • Haskell threads communicate through Mvars (mutable variables). • MVar writes and reads occur atomically • A MVar may be empty or it may contain a value • write to occupied MVar, read from empty MVar: • will be blocked • will be rewoken when it’s empty/ a value is written and try again • wake up scheme: FIFO

  6. MVar Operations • data MVar a • newEmptyMVar :: IO (MVar a) • newMVar :: a −> IO (MVar a) • takeMVar :: MVar a −> IO a • putMVar :: MVar a −> a −> IO () • readMVar :: MVar a −> IO a • tryTakeMVar :: MVar a −> IO (Maybe a) • tryPutMVar :: MVar a −> a −> IO Bool • isEmptyMVar :: MVar a −> IO Bool • …

  7. Example: Make A Rendezvous main :: IO() main = do aMVar <- newEmptyMVar bMVar <- newEmptyMVar doneMVar <- newEmptyMVar forkIO (threadA aMVar bMVar doneMVar) forkIO (threadB aMVar bMVar) takeMVar doneMVar ... module Main where import Control.Concurrent import Control.Concurrent.MVar threadA :: MVar String -> MVar String -> MVar Int -> IO() threadA valueToSendMVar valueReceiveMVar doneMVar = do putMVar valueToSendMVar "Are you going trick or treating tonight?” v <- takeMVar valueReceiveMVar putMVar doneMVar 1 threadB :: MVar String -> MVar String ->IO() threadB valueToReceiveMVar valueToSendMVar = do z <- takeMVar valueToReceiveMVar putMVar valueToSendMVar “Yes. Let’s meet at 8pm.”

  8. Message Passing Channels • unbounded FIFO channel • data Chan a • newChan :: IO (Chan a) • writeChan :: Chan a -> a -> IO () • readChan :: Chan a -> IO a • unGetChan :: Chan a -> a -> IO () • isEmptyChan :: Chan a -> IO Bool • dupChan :: Chan a -> IO (Chan a) • ... Ex: chat.hs

  9. Haskell STM • Programming with MVar can lead to deadlock • one thread is waiting for a value to appear in an MVar • no other thread will ever write a value to that MVar • An alternative way to synchronize: software transactional memory (STM) • A special type of shared variable: TVar • TVars are used only inside atomic blocks. • The code inside an atomic block is executed as if it were an atomic instruction. • Functionally, no other thread is running in parallel/interleaved. • In reality, a log is used to roll back execution if conflicts.

  10. TVar Operations • data STM a −− A monad supporting atomic memory transactions • atomically :: STM a −> IO a −− Perform a series of STM actions atomically • data TVar a −− Shared memory locations that support atomic memory operations • newTVar :: a −> STM (TVar a) −− Create a new TVar with an initial value • readTVar :: TVar a −> STM a −− Return the current value stored in a TVar • writeTVar :: TVar a −> a −> STM () −− Write the supplied value into a TVar

  11. bal :: TVar Int 8 7 Thread 1 1 atomically (do 2v <- readTVar bal 3writeTVar bal (v+1) 4 ) Thread 2 1 atomically (do 2v <- readTVar bal 3writeTVar bal (v-3) 4 ) • Attempt to commit Thread 2 fails, because value in memory is not consistent with the value in the log • Transaction re-runs from the beginning • Thread 1 commits • Shared bal variable is updated • Transaction log is discarded 7 8 7 4 bal transaction log of Thread 1 transaction log of Thread 2

  12. bal :: TVar Int 5 8 Thread 1 1 atomically (do 2v <- readTVar bal 3writeTVar bal (v+1) 4 ) Thread 2 1 atomically (do 2v <- readTVar bal 3writeTVar bal (v-3) 4 ) 5 transaction log of Thread 2 Ex: simpleSTM.hs

  13. When To Use retry and orElse? • retry :: STM a • abort the current transaction • re-execute it from the beginning using a fresh log withdraw :: TVarInt −> Int −> STM () withdraw acc n = do { bal <− readTVar acc; if bal < n then retry; writeTVar acc (bal-n) } Ex: account.hs • orElse :: STM a -> STM a -> STM a • compose two transactions • if one transaction aborts then the other transaction is executed • if it also aborts then the whole transaction is re-executed atomically (do { withdraw a1 3 ‘orElse‘ withdraw a2 3; deposit b 3 } )

  14. Case Study: ArrayBlockingQueue (Discolo et al. FLOPS 06) • from JSR-166, a java implementation of a fixed length queue • select 3 representative interfaces: • take: Removes an element from the head of the queue, blocking if the queue is empty • peek: Removes an element from the head of the queue if one is immediately available, otherwise return Nothing • pullTimeout: Retrives and removes the head of this queue, waiting up to the specified wait time if necessary for an element to become available

  15. Data Structure data ArrayBlockingQueueIOe = ArrayBlockingQueueIO{ iempty :: QSem, ifull :: QSem, ilock :: MVar (), ihead :: IORefInt, itail :: IORefInt, iused :: IORefInt, ilen :: Int, ia :: IOArrayInte } data ArrayBlockingQueueSTM e = ArrayBlockingQueueSTM { shead :: TVar Int, stail :: TVar Int, sused :: TVar Int, slen :: Int, sa :: Array Int (TVar e) }

  16. function: take takeIO :: ArrayBlockingQueueIOe -> IO e takeIOabq = do b <- waitQSem (iemptyabq) e <- withMVar (ilockabq) (\dummy -> readHeadElementIOabq True) return e takeSTM :: ArrayBlockingQueueSTMe -> IO e takeSTMabq = do me <- atomically ( readHeadElementSTMabq True True) case me of Just e -> return e

  17. function: peek peekIO :: ArrayBlockingQueueIO e -> IO (Maybe e) peekIO abq = do b <- tryWaitQSem (iempty abq) if b then do me <- withMVar (ilock abq) (\dummy -> do u <- readIORef (iused abq) if u == 0 then return Nothing else do e <- readHeadElementIO abq False return (Just e)) signalQSem (iempty abq) return me else return Nothing peekSTM :: ArrayBlockingQueueSTMe -> IO (Maybe e) peekSTMabq = atomically (readHeadElementSTMabq False False)

  18. helper function: readHeadElement readHeadElementIO :: ArrayBlockingQueueIOe -> Bool -> IO e readHeadElementIOabq remove = do h <- readIORef (iheadabq) e <- readArray (iaabq) h if remove then do let len = ilenabq newh = h `mod` len u <- readIORef (iusedabq) writeIORef (iheadabq) newh writeIORef (iusedabq) (u-1) signalQSem (ifullabq) else return () return e readHeadElementSTM :: ArrayBlockingQueueSTMe -> Bool -> Bool -> STM (Maybe e) readHeadElementSTMabq remove block = do u <- readTVar (susedabq) if u == 0 then if block then retry else return Nothing else do h <- readTVar (sheadabq) let tv = saabq ! h e <- readTVartv if remove then do let len = slenabq let newh = h `mod` len writeTVar (sheadabq) $! newh writeTVar (susedabq) $! (u-1) else return () return (Just e)

  19. A More Complex Function: pollTimeout • lock-based mechanism has no support for composing two concurrency abstractions pollTimeoutSTM :: ArrayBlockingQueueSTMe -> TimeDiff -> IO (Maybe e) pollTimeoutSTMabq timeout = do c <- startTimerIO timeout atomically ((do readTChanc return Nothing) `orElse` (do me <- readHeadElementSTMabq True True return me) )

  20. Performance Measurements(Discolo et al. FLOPS 06) • The test • creates an ArrayBlockingQueue of type integer • creates an equal number of reader and writer threads that simply loops for the specific number of iterations performing taking or put operations on the queue • completes when all threads have terminated • For each processor configuration (1-8 processors) • varies only the number of reader/writer threads

  21. STM with Data Invariants • STM can also deal with consistency of the program • check E where E is an invariant that should be preserved by every atomic update • check :: Bool -> STM a • check True = return () • check False = retry • account.hs with invariants

  22. References • “A Tutorial on Parallel and Concurrent Programming in Haskell”, Jones et al., AFP summer school notes, 2008 • “Lock Free Data Structures using STM in Haskell”, Discolo et al., FLOPS 2006 • http://haskell.org/haskellwiki/Haskell_for_multicores • ...

More Related