parallelism concurrency
play

Parallelism & Concurrency Advanced functional programming - - PowerPoint PPT Presentation

Parallelism & Concurrency Advanced functional programming - Lecture 9 Trevor L. McDonell (& Wouter Swierstra) 1 Parallelism & Concurrency Parallelism vs. Concurrency Related concepts, but not the same Both give up the


  1. Parallelism & Concurrency Advanced functional programming - Lecture 9 Trevor L. McDonell (& Wouter Swierstra) 1

  2. Parallelism & Concurrency • Parallelism vs. Concurrency • Related concepts, but not the same • Both give up the strictly sequential execution model of the Von Neumann machine • Concurrent programming: • Structuring a program into different, interacting tasks • Tasks may be executed simultaneously or interleaved • In general non-deterministic • Examples: OS kernel, GUI, web server • Parallel programming: • Improving the execution speed of an application • Simultaneous use of multiple physical processing elements • Examples: Video encoder, image processing, simulation codes 2

  3. Overview Haskell provides many different tools for concurrency and parallelism: • Basic concurrency primitives (locks) • Software transactional memory (STM) • Erlang-style message passing (Cloud Haskell) • Primitives to control evaluation strategies • Data-parallel arrays • GPU programming • … 3

  4. Overview • I highly recommend Simon Marlow’s book Parallel and Concurrent Programming in Haskell , which you can read it free online: https://simonmar.github.io/pages/pcph.html 4

  5. Concurrency 5

  6. Control.Concurrent -- creating a thread forkIO :: IO () -> IO ThreadId -- managing the current thread threadDelay :: Int -> IO () -- delay in microseconds yield :: IO () myThreadId :: IO ThreadId -- managing other threads throwTo :: Exception e => ThreadId -> e -> IO () Working with threads 6

  7. forkIO :: IO () -> IO ThreadId Forking threads • Using threads forces you to use IO • Any thread can create new threads • If the main program ends, all its threads are stopped too • You can explicitly control other threads by sending them exceptions via their ThreadId (e.g. to kill the thread) 7

  8. Haskell threads Haskell threads created with forkIO are not OS threads! • These threads are very lightweight; they are created and scheduled by the GHC runtime system • If you use the threaded version of the runtime system (pass -threaded to the compiler), multiple OS threads may be used behind the scenes • GHC’s runtime is very clever: there are many options provided for configuring it and obtaining debug information 8

  9. Sharing data between threads If we fork off a thread of type IO () , how do we observe its result? • We can create explicit references to mutable memory in Haskell using IORef s to share memory between threads • Using IORef s to share data between threads is unsafe! In the sense that it can lead to race conditions and other inconsistent states • Generally, when working with threads, you have to be careful that they don’t interfere with each other 9

  10. Data.IORef newIORef :: a -> IO (IORef a) readIORef :: IORef a -> IO a writeIORef :: IORef a -> a -> IO () modifyIORef :: IORef a -> (a -> a) -> IO () Mutable variables in Haskell • A value of type IORef a is a mutable reference (pointer) to a value of type a • Because references are mutable, all operations have results in IO 10

  11. test :: Int -> IO () test n = do x <- newIORef 0 mapM_ (forkIO . loop x) [1..n] loop :: IORef Int -> Int -> IO () loop ref m = do writeIORef ref m loop ref m Example loop x 0 n <- readIORef ref when (m /= n) $ putStrLn (show m) Question: What, if anything, will the following code produce? 11

  12. Shared state concurrency Non-determinism makes it much harder to develop correct programs • Threads communicate via a shared state • Problem: inconsistent data structures, race conditions 12

  13. Shared state concurrency We require a lock (of some kind) to control access to the shared state 13

  14. :: MVar a -> a -> IO () -- wait if already full newMVar :: a -> IO (MVar a) newEmptyMVar :: IO (MVar a) takeMVar :: MVar a -> IO a -- wait if empty putMVar Control.Concurrent.MVar Synchronised mutable variables in Haskell • More flexible than IORef , and can be used to implement concurrency primitives such as locks and semaphores • An MVar may be either empty or full : a thread will block trying to read from an empty MVar , or trying to write to a full one • The runtime system manages blocked threads with some fairness guarantee 14

  15. Example: Bank account Model a bank account and operations like withdrawal, deposit, and transfer of funds between accounts. It should not be possible to observe a state where, during a transfer, money has been withdrawn from one account without yet being deposited into the target account. 15

  16. withdraw :: Int -> MVar Int -> IO Bool withdraw amount account = modifyMVar account $ \balance -> -- acquires lock if balance >= amount then return (balance - amount, True) else return (balance, False) transfer :: Int -> MVar Int -> MVar Int -> IO Bool transfer amount from to = do withdraw amount from -- inconsistent state mustn't be observable! deposit amount to Example: Bank account 16

  17. • Locks must be acquired in a fixed (global) order, otherwise there is a potential for deadlock transfer :: Int -> MVar Int -> MVar Int -> IO Bool transfer amount from to = withMVar from $ \balance_from -> withMVar to $ \balance_to -> ... Example: Bank account We need to implement transfer differently • Question: What happens if someone simultaneously tries to transfer funds in the opposite direction? 17

  18. transfer :: Int -> MVar Int -> MVar Int -> IO Bool transfer amount from to = withMVar from $ \balance_from -> withMVar to $ \balance_to -> ... Example: Bank account We need to implement transfer differently • Question: What happens if someone simultaneously tries to transfer funds in the opposite direction? • Locks must be acquired in a fixed (global) order, otherwise there is a potential for deadlock 17

  19. transfer Concurrency using locks The good: • ..? The bad: • Taking too many or too few locks • Taking the wrong locks, or in the wrong order • Difficult error recovery • Lost wake-ups and erroneous retries The ugly: • Lock’s don’t support modular programming • We had to inline the definition of withdraw and deposit into 18

  20. readTVar :: TVar a -> a -> STM () atomically :: STM a -> IO a -- run a transaction newTVar :: a -> STM (TVar a) -- STM equivalent of IORef newTVarIO :: a -> IO (TVar a) Control.Concurrent.STM :: TVar a -> STM a writeTVar Software Transactional Memory (STM) • A concurrency abstraction which takes ideas from database systems • Threads execute transactions , whose effects can be undone if necessary • atomicity: all effects of executing a transaction become visible at once • isolation: can not see the effects of other threads 19

  21. transfer :: Int -> TVar Int -> TVar Int -> IO Bool transfer amount from to = atomically $ do -- run a transaction ok <- withdraw amount from -- no locks! when ok $ deposit amount to return ok Example: Bank account, revisted • Modular concurrency! • Be optimistic: locks are pessimistic 20

  22. retry :: STM a orElse :: STM a -> STM a -> STM a Software Transactional Memory • The retry function rolls back the effects of the current transaction and restarts the atomic operation • orElse offers an alternative to immediate execution: if the first alternative leads to a retry , attempt the second These operations allow you to assemble more complex transactions 21

  23. STM is lock free An alternative to concurrent programming using lock/mutex/synchronised methods Compositional and modular concurrent programming • Software transactional memory does not use locking: deadlocks can not occur • Robust in the presence of failure or cancellation • However, large transactions can take a huge number of retries, so STM works best if transactions are small, or unlikely to interfere The concept of STM has been around since ’95 • Can it be implemented efficiently? 22

  24. STM implementation Naive implementation: a single global lock Better implementation: • Each transaction keeps a log of all of the memory accesses during a transaction (record initial value and latest update), but does not actually perform any writes yet • At the end of the transaction, validate the log: if the initial values are the same as the current values, the memory is still consistent and the transaction is committed; otherwise, it is restarted • Validation and committing must be truly atomic 23

  25. • Haskell’s type system is particularly well suited to statically check the restrictions required by STM because side effects are controlled derp = atomically $ do derp :: IO a brexit ?? -- :: STM a retry (international) side effects -- :: IO () STM in other languages Software transactional memory is supported in many languages, including C/C++, C#, Java, Perl, Python, Scala, OCaml, SmallTalk … Transactions try to commit, but roll-back and retry later if the log is no longer consistent Question: Why might this cause problems (in other languages)? 24

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend