Transcript
Page 1: Explicit Concurrent Programming in Haskell

QIAN XICOS597C 10/28/2010

Explicit Concurrent Programming in Haskell

Page 2: Explicit Concurrent Programming in Haskell

Outline

Recap of IO Monad

Thread Primitives

Synchronization with Locks

Message Passing Channels

Software Transactional Memory

Transactional Memory with Data Invariants

Page 3: Explicit Concurrent Programming in Haskell

IO Monad in Haskell

Why Monad? Pure functional language needs determinism.

getChar :: IO CharputChar :: Char -> IO ()

main :: IO ()main = do c <- getChar putChar c

f x = e

f 3...

f 3

What is Monad? an abstract data type: IO a, e.g. IO Int a container of impure, suspended actions/computations

How to use Monad?

= 7

= 7?

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

Page 4: Explicit Concurrent Programming in Haskell

Creating Haskell Threads

forkIO :: IO () -> IO ThreadId

effect-fulcomputati

on

identification of a Haskell threadmust be used in an IO monad

forkOS :: IO () -> IO ThreadId support certain kinds of foreign calls to external code.

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.

Ex: fibEuler.hs

Page 5: Explicit Concurrent Programming in Haskell

Mutable Variable

Haskell threads communicate through Mvars (mutable variables).

MVar writes and reads occur atomicallyA MVar may be empty or it may contain a

valuewrite 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

Page 6: Explicit Concurrent Programming in Haskell

MVar Operations

data MVar anewEmptyMVar :: IO (MVar a)newMVar :: a −> IO (MVar a) takeMVar :: MVar a −> IO a putMVar :: MVar a −> a −> IO ()readMVar :: MVar a −> IO atryTakeMVar :: MVar a −> IO (Maybe a)tryPutMVar :: MVar a −> a −> IO BoolisEmptyMVar :: MVar a −> IO Bool…

Page 7: Explicit Concurrent Programming in Haskell

Example: Make A Rendezvous

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.”

main :: IO() main = do aMVar <- newEmptyMVar bMVar <- newEmptyMVar doneMVar <- newEmptyMVar forkIO (threadA aMVar bMVar doneMVar) forkIO (threadB aMVar bMVar) takeMVar doneMVar ...

Page 8: Explicit Concurrent Programming in Haskell

Message Passing Channels

unbounded FIFO channeldata Chan anewChan :: IO (Chan a)writeChan :: Chan a -> a -> IO ()readChan :: Chan a -> IO aunGetChan :: Chan a -> a -> IO ()isEmptyChan :: Chan a -> IO BooldupChan :: Chan a -> IO (Chan a)...

Ex: chat.hs

Page 9: Explicit Concurrent Programming in Haskell

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.

Page 10: Explicit Concurrent Programming in Haskell

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

Page 11: Explicit Concurrent Programming in Haskell

7

bal :: TVar Int

Thread 11 atomically (do2 v <- readTVar bal3 writeTVar bal (v+1)4 )

What Value Read

Value Write

bal

What Value Read

Value Write

Thread 21 atomically (do2 v <- readTVar bal3 writeTVar bal (v-3)4 )

7

transaction log of Thread 2

7

transaction log of Thread 1

8 4

8

• Thread 1 commits• Shared bal variable is updated• Transaction log is discarded bal

• 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

Page 12: Explicit Concurrent Programming in Haskell

bal :: TVar Int

8

Thread 11 atomically (do2 v <- readTVar bal3 writeTVar bal (v+1)4 )

Thread 21 atomically (do2 v <- readTVar bal3 writeTVar bal (v-3)4 )

What Value Read

Value Write

bal 8

transaction log of Thread 2

5

5

Ex: simpleSTM.hs

Page 13: Explicit Concurrent Programming in Haskell

When To Use retry and orElse?

retry :: STM a abort the current

transaction re-execute it from the

beginning using a fresh log

withdraw :: TVar Int −> Int −> STM () withdraw acc n = do { bal <− readTVar acc; if bal < n then retry; writeTVar acc (bal-n) }

atomically (do { withdraw a1 3 ‘orElse‘ withdraw a2 3; deposit b 3 } )

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

Ex: account.hs

Page 14: Explicit Concurrent Programming in Haskell

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

Page 15: Explicit Concurrent Programming in Haskell

Data Structure

data ArrayBlockingQueueIO e = ArrayBlockingQueueIO{

iempty :: QSem, ifull :: QSem, ilock :: MVar (), ihead :: IORef Int, itail :: IORef Int, iused :: IORef Int, ilen :: Int, ia :: IOArray Int e }

data ArrayBlockingQueueSTM e = ArrayBlockingQueueSTM {

shead :: TVar Int, stail :: TVar Int, sused :: TVar Int, slen :: Int, sa :: Array Int (TVar e)}

Page 16: Explicit Concurrent Programming in Haskell

function: take

takeIO :: ArrayBlockingQueueIO e -> IO e

takeIO abq = do b <- waitQSem (iempty abq) e <- withMVar (ilock abq) (\dummy -> readHeadElementIO abq True) return e

takeSTM :: ArrayBlockingQueueSTM e -> IO e takeSTM abq = do me <- atomically ( readHeadElementSTM abq True True) case me of Just e -> return e

Page 17: Explicit Concurrent Programming in Haskell

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 :: ArrayBlockingQueueSTM e -> IO (Maybe e) peekSTM abq = atomically (readHeadElementSTM abq False False)

Page 18: Explicit Concurrent Programming in Haskell

helper function: readHeadElement

readHeadElementIO :: ArrayBlockingQueueIO e -> Bool -> IO e

readHeadElementIO abq remove= do h <- readIORef (ihead abq) e <- readArray (ia abq) h if remove then do let len = ilen abq newh = h `mod` len u <- readIORef (iused abq) writeIORef (ihead abq) newh writeIORef (iused abq) (u-1) signalQSem (ifull abq) else return () return e

readHeadElementSTM :: ArrayBlockingQueueSTM e -> Bool -> Bool -> STM (Maybe e) readHeadElementSTM abq remove block = do u <- readTVar (sused abq) if u == 0 then if block then retry else return Nothing else do h <- readTVar (shead abq) let tv = sa abq ! h e <- readTVar tv if remove then do let len = slen abq let newh = h `mod` len writeTVar (shead abq) $! newh writeTVar (sused abq) $! (u-1) else return () return (Just e)

Page 19: Explicit Concurrent Programming in Haskell

A More Complex Function: pollTimeout

lock-based mechanism has no support for composing two concurrency abstractions

pollTimeoutSTM :: ArrayBlockingQueueSTM e -> TimeDiff -> IO (Maybe e) pollTimeoutSTM abq timeout = do c <- startTimerIO timeout atomically ((do readTChan c return Nothing) `orElse` (do me <- readHeadElementSTM abq True True return me) )

Page 20: Explicit Concurrent Programming in Haskell

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

Page 21: Explicit Concurrent Programming in Haskell
Page 22: Explicit Concurrent Programming in Haskell

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

Page 23: Explicit Concurrent Programming in Haskell

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

...


Top Related