!!"!#"$%& cs242 ! ! Multi-cores are coming! ! - For 50 years, hardware designers delivered 40-50% increases per year in sequential program speed. ! - Around 2004, this pattern failed because power and cooling issues made it impossible to increase clock Kathleen Fisher ! frequencies. ! - Now hardware designers are using the extra transistors that Moore’ s law is still delivering to put more processors on a single chip. ! Reading: “Beautiful Concurrency”, ! If we want to improve program speed, “The Transactional Memory / Garbage Collection Analogy” ! concurrent programs are no longer optional. ! Thanks to Simon Peyton Jones for these slides. ! Libraries build ! Concurrent programming is essential to layered concurrency improve performance on a multi-core. ! abstractions ! Library ! ! Yet the state of the art in concurrent Library ! programming is 30 years old: Library ! locks and condition variables. " " Library ! (In Java: synchronized , wait , and notify .) ! Library ! Library ! Library ! ! Locks and condition variables are Concurrency primitives ! fundamentally flawed: it’ s like building a sky- scraper out of bananas. ! Hardware ! This lecture describes significant recent progress: bricks and mortar instead of bananas. ! At Atomic Blocks ! Locks and condition variables ! Atomic blocks ! (a) are hard to use and ! (a) are easier (b) do not compose. ! to use and ! Library ! Library ! (b) they do y ! compose. ! r a r Library ! b Library ! i L Library ! L i b r a Library ! r y ! Library ! Library ! Library ! Library ! Library ! Library ! e s ! a b l Atomic blocks ! a r i n v t i o n d i c o n d s a c k L o 3 primitives: atomically, retry, orElse ! Hardware ! Hardware ! !&
! !!"!#"$%& ! Consider a (correct) Java bank Account class: ! A 30-second review: ! class Account{ ! float balance; ! ! Ra Races: forgotten locks lead to inconsistent views ! ! Dea eadlock: locks acquired in “wrong” order ! synchronized void deposit(float amt) { ! balance += amt; ! ! Lost wakeu eups: : forgotten notify to condition variables ! } ! ! Di Diabol bolical e error r recovery: need to restore invariants synchronized void withdraw(float amt) { ! and release locks in exception handlers ! if (balance < amt) ! throw new OutOfMoneyError(); ! balance -= amt; ! } ! ! These are serious problems. But even worse... ! } ! ! Now suppose we want to add the ability to transfer funds from one account to another. ! ! Simply calling withdraw and deposit to ! Synchronizing transfer can cause deadlock: ! implement transfer causes a race condition: ! class Account{ ! float balance; ! class Account{ ! synchronized void deposit(float amt) { ! float balance; ! balance += amt; ! synchronized void deposit(float amt) { ! } ! balance += amt; ! synchronized void withdraw(float amt) { ! } ! if (balance < amt) ! synchronized void withdraw(float amt) { ! throw new OutOfMoneyError(); ! if (balance < amt) ! balance -= amt; ! throw new OutOfMoneyError(); ! } ! balance -= amt; ! synchronized ! } ! void transfer_wrong2(Acct other, float amt) { ! void transfer_wrong1(Acct other, float amt) { ! // can deadlock with parallel reverse-transfer ! other.withdraw(amt); ! this.deposit(amt); ! // race condition: wrong sum of balances ! other.withdraw(amt); ! this.deposit(amt);} ! } ! } ! } ! Scalable double-ended queue: one lock per cell ! Diffi fficulty of f qu queu eue e Codin Co ding g styl yle ! imp mplem emen entation ! Sequential code ! Undergraduate ! No interference if ends “far enough” apart ! But watch out when the queue is 0, 1, or 2 elements long! ! '&
!!"!#"$%& Diffi fficulty of f qu queu eue e Diffi fficulty of f qu queu eue e Co Codin ding g styl yle ! Diffi fficulty of f concurren ent qu queu eue ! Codin Co ding g styl yle ! Codin Co ding g styl yle ! imp mplem emen entation ! imp mplem emen entation ! Sequential code ! Undergraduate ! Sequential code ! Undergraduate ! Sequential code ! Undergraduate ! Locks and condition Publishable result at Locks and condition Publishable result at Locks and condition Publishable result at variables ! international conference ! variables ! international conference 1 ! variables ! international conference 1 ! Atomic blocks ! Undergraduate ! 1 Simp mple, fast, and practical non-blocking and blocking concurren ent qu queu eue e algorithms ms. ! 1 Simp mple, fast, and practical non-blocking and blocking concurren ent qu queu eue e algorithms ms. ! Like database Optimistic ! transactions ! concurrency ! atomically {... <code> ...} ! atomically {...sequential code...} ! One possibility: ! ! To a first approximation, just write the sequential code, ! Execute <code> without taking any locks. ! and wrap atomically around it. ! read y; ! Log each read and write in <code> to a read z; ! All-or-nothing semantics: At Atomic commit. ! write 10 x; thread-local transaction log. ! write 42 z; … ! Atomic block executes in Is Isolation. ! ! Writes go to the log only, not to memory. ! ! Cannot deadlock (there are no locks!). ! A C I D ! ! At the end, the transaction validates the log. ! ! Atomicity makes error recovery easy ! If valid, atomically co commits changes to memory. ! - (e.g. throw exception inside sequ equen ential code). ! If not valid, re-runs from the beginning, discarding changes. ! - ! Logging memory effects is expensive. ! ! Haskell already partitions the world into ! Realizing STM ! - immutable values (zillions and zillions) ! Haskell programmers brutally trained from - mutable locations (some or none) ! birth to use memory in ! Only need to log the latter! ! effects sparingly. ! ! Type system controls where I/O effects happen. ! Haskell ! ! Monad infrastructure ideal for constructing transactions & implicitly passing transaction log. ! ! Already paid the bill. Simply reading or writing a mutable location is expensive (involving a procedure call) so transaction overhead is not as large as in an imperative language. ! (&
!!"!#"$%& newIORef :: a -> IO (IORef a) " readIORef :: IORef a -> IO a " writeIORef :: IORef a -> a -> IO () ! ! Consider a simple Haskell program: ! Recall that Haskell IO Monad functions newIORef , readIORef , and writeIORef manage mutable state. ! main = do { putStrLn (reverse “yes”); " ! putStrLn “no” } ! main = do { r <- newIORef 0; ! ! ! Effects are explicit in the type system. ! incR r; ! s <- readIORef r; ! (reverse “yes”) :: String — No effects ! print s } " (putStr “no” ) :: IO () — Effects okay ! incR :: IORef Int -> IO () ! ! Main program is a computation with effects. ! incR r = do { v <- readIORef r; ! writeRef r (v+1) } ! main :: IO () ! Reads and writes are 100% explicit. The type system disallows (r + 6) because r :: IORef Int . ! ! The forkIO function spawns a thread. ! ! Idea: add a function atomically that executes its argument computation atomically. ! ! It takes an IO action as its argument. ! forkIO :: IO () -> IO ThreadId ! atomically :: IO a -> IO a — almost ! main = do { r <- newIORef 0; ! main = do { ! forkIO (incR r); ! r <- newIORef 0; ! A race ! incR r; ! forkIO (atomically (incR r)); ! atomically (incR r); ! ... } ! ... } ! incR :: IORef Int -> IO () ! incR r = do { v <- readIORef r; ! Worry: What prevents using incR outside atomically , which writeIORef r (v+1) } ! would allow data races between code inside atomic and outside? ! ! Introduce a type for imperative transaction variables atomically :: STM a -> IO a " ( TVar ) and a new Monad ( STM ) to track transactions. ! newTVar :: a -> STM (TVar a) " readTVar :: TVar a -> STM a " ! Ensure TVars can only be modified in transactions. ! writeTVar :: TVar a -> a -> STM () ! ! Can’ t fiddle with TVars outside atomic block. [good] ! atomically :: STM a -> IO a " newTVar :: a -> STM (TVar a) " ! Can’ t do IO or manipulate regular imperative readTVar :: TVar a -> STM a " writeTVar :: TVar a -> a -> STM () ! variables inside atomic block. [sad, but also good] ! incT :: TVar Int -> STM () ! atomically (if x<y then launchMissiles) ! incT r = do { v <- readTVar r; ! ! ...and, best of all... ! writeTVar r (v+1) } ! main = do { r <- atomically (newTVar 0); ! forkIO (atomically (incT r)); ! atomically (incT r); ! ... } ! )&
Recommend
More recommend