Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
1
Lecture 12
Transactional Memory
Shared Memory Concurrency
• Lock-based programming is difficult
• There are many potential problems:◦ Deadlock
◦ Starvation
◦ Priority inversion
◦ Convoying
◦ Non-compositionality
• Is there some way to eliminate at least some of these problems?
2PPHT09 – TM
Convoying
• Convoying occurs when a process has taken a mutex and is then pre-empted by the scheduler
• It has the effect that other processes may not be allowed to enter the mutex
• This inhibits concurrency
3PPHT09 – TM
Non-compositionality
• Lock-based programming doesn't compose
• Example:◦ Suppose you have two thread safe buffers and
you want to atomically take an element from one f h d h hof them and put it in the other
4PPHT09 – TM
class Buffer<E> {public E get();public void put(E item);
}
Non-compositionality
• A not so nice solution:◦ Expose the locks of the buffers
◦ Lock both buffers before moving the element
5PPHT09 – TM
class Buffer<E> {public void lock();public void unlock();public E get();public void put(E item);
}
Non-compositionality
• Another not so nice solution◦ Create a new lock which must be taken each time
any of the two buffers are accessed
• The number of locks grows as we compose algorithms◦ Takes time
◦ Increases the risk of programming errors
6PPHT09 – TM
2
Optimistic Concurrency
• Lock-based synchronization:Pessimistic Concurrency◦ We always assume things go wrong and thus we
need mutual exclusion
• An alternative: Optimistic Concurrency◦ Assume we have mutual exclusion
◦ Perform our critical section
◦ Check if everything was OK
◦ Revert our actions if it wasn't
◦ Otherwise proceed7PPHT09 – TM
Non-blocking synchronisation
• It is possible to write shared-memory algorithms without locks◦ Usually using special hardware instructions — CAS
• In many circumstances such algorithms are faster than lock-based alternatives◦ They allow for more concurrency
• Very difficult to do in general
8PPHT09 – TM
Transactional Memory
• A concept for easy non-blocking programming
• Implementations◦ Hardware
• Sun’s Rock processor? (We need to visit The Oracle)
◦ Software• Using special HW instructions — CAS
• Using locks
• Still a hot research topic but already useful
9PPHT09 – TM
Software Transactional Memory
• Access for the programmer◦ As a library
• Java: jvstm, JSTM (XSTM), DSTM2
• C/C++: TinySTM, LibLTX, LibCTM, RSTM
• C# Python Lisp Ocaml Perl• C#, Python, Lisp, Ocaml, Perl, …
• Haskell
◦ As a language construct• Java CCR — not available, yet
10PPHT09 – TM
Transactions
• A standard database concept◦ A group of operations should execute atomically,
◦ Or not at all
• Transactional Memory takes this idea to operations on memory and specifically shared variables
11PPHT09 – TM
Transactions — Workings
• When writing to variables, don't actually modify them, instead:
• Keep a log over all the reads and writes that the transaction makes
• When the transaction is done:1) Validate: check that any read variables still have
the same value
2) Commit: make the changes permanent
◦ If the validation failed rerun the transaction
12PPHT09 – TM
3
Transactions — Example
v = 1x = 2
P1 P2
13PPHT09 – TM
i = read xi = i + 1store i in xstore i in v
LOG
read x 2 read x 2
LOG
store x 3 store x 3
store v 3 store v 3
Transactions — Example
P1 P2
v = 3x = 3
14PPHT09 – TM
i = read xi = i + 1store i in xstore i in v
LOG
read x 2
LOG
store x 3
store v 3
Transactions — Example
v = 3x = 3
P2P1
15PPHT09 – TM
i = read xi = i + 1store i in xstore i in v
read x 3
LOG
store x 4
store v 4
LOG
Transactions — Example
v = 4x = 4
P2P1
16PPHT09 – TM
i = read xi = i + 1store i in xstore i in v
LOGLOG
Conditional Critical Regions
• STM is often presented using CCR
• Introduced by the atomic keyword
• Defines a transaction guarded by a condition
i ( di i ) {
• Whoa. Deja vu.
• A deja vu is usually a glitch in the Matrix17PPHT09 – TM
atomic (condition) {statements
}
Specifying Synchronisation
• The book uses a notation to specify atomic actions◦ Not part of JR/MPD
◦ Purely for describing the desired behaviour of a
from lecture 2
18PPHT09 – Shared Update
program
<S> – statement S is executed atomically
<await (B) S> – execute <S>, starting onlywhen B is true
4
Implementing await
• await statement is very expressive◦ Mutual exclusion
◦ Conditional synchronisation
• Difficult to implement in general
from lecture 2
19PPHT09 – Shared Update
• Though, some special cases are easy◦ await statement without body
•<await (B) ;>• Sufficient for solving the shared update problem in
low-level programming
◦ Other interesting cases will come later
STM — Programming
• So you say await is expressive and easy to use in programming
• But we did not see anything that supports this◦ Give us some code to chew on
• OK◦ How about a question from an older exam
◦ (students did really well on this question)
20PPHT09 – TM
Readers/Writers Problem
• Another classic synchronisation problem
• Two kinds of processes share access to a “database”◦ Readers examine the contents
21PPHT09 – TM
◦ Multiple readers allowed concurrently
◦ Writers examine and modify
◦ A writer must have mutex
• Invariant◦ (nr==0 ∨ nw==0) ∧ nw<=1
Readers/Writers Controller
• Database is globally accessible◦ Cannot be “internal to the input statement”
• Encapsulate only the access protocol◦ Similar to the monitor and MP solution
22PPHT09 – TM
public void start_read();public void end_read();public void start_write();public void end_write();
Readers/Writers on One Slide
private process RWserver {int nr = 0;int nw = 0;while (true) {
inni void start read() st
from lecture 8
23PPHT09 – Message Passing
inni void start_read() stnw==0 { nr++; }
[] void end_read() { nr--; }[] void start_write() st
nr==0 && nw==0 { nw++; }[] void end_write() { nw--; }
}}
Readers/Writers on One Slide, too
class RW {private int nr = 0, nw = 0;public void startRead() {
atomic (nw == 0) { nr++; }}public void endRead() {
atomic { nr--; }}
24PPHT09 – TM
atomic { nr ; }}public void startWrite() {
atomic (nr == 0 && nw == 0) {nw++;
}}public void endWrite() {
atomic { nw--; }}}
Whoa. Deja vu.
5
R/W – Coarse-grained Solution
process R((int i=0;i++;i<M)) {while(true){
<await (nw==0) nr++;>//read database<nr--;>
}}
from lecture 3
25PPHT09 – Semaphores
}}
process W((int i=0;i++;i<N)) {while(true){
<await (nr==0 && nw==0) nw++;>//write database<nw--;>
}} On one slide, too.
TM and Side Effects
• await statement is great
• Let’s help the Therac-25 programmersWhy Reasoning?
• Horror stories
26PPHT09 – TM
2PPHT09 – Reasoning
Horror stories◦ The Therac-25 incident
• What can happen when programmers are not even aware of the principles of concurrent programming
◦ The Gaisal train crash• August 1999
• Error: assumption about speed of train
• Investigation report hard to find
TM and Side Effects
public void start_treatment() {atomic (all_set_condition) {
...fire_the_beam()...
}
• How many times will it fire the beam?
• When will it be fired?
27PPHT09 – TM
}}
TM and Side Effects
• How many times will we be prompted to input something?
public String read_text() {t i {
28PPHT09 – TM
atomic {...char = input_from_keyboard()...
}}
TM and Side Effects
• Transactions cannot perform any operation that cannot be undone
⇒
• Side effects such as I/O do not mix well with transactional memory
29PPHT09 – TM
Isolating Side Effects — Haskell
• Functional
• Pure◦ Side effects cannot occur everywhere
◦ Ideally suited for STM
• GHC◦ A Haskell compiler
◦ Includes STM support in the run-time system• Though, the current implementation seems to have
some (additional) issues with fairness
◦ Exposes STM functionality as a library
30PPHT09 – TM
6
Haskell
• Pure and side effecting computations are separated by the type system
”a string” :: StringreadLine :: IO String
31PPHT09 – TM
readLine :: IO StringputStrLn :: String -> IO ()
The type constructor IOindicates that this function can
perform side effects
Haskell
• I/O is isolated using the type system
• Therefore STM can be easily isolated from I/O
• But Haskell does not allow variables to be updated everywhere
• Solution◦ Add a new additional type constructor STM which
allows separation
◦ Shared variables can only be accessed inside STM
32PPHT09 – TM
Haskell STM
module Control.Concurrent.STM
data STM adata TVar a
newTVarIO :: a -> IO (TVar a)newTVar a > STM (TVar a)
33PPHT09 – TM
newTVar :: a -> STM (TVar a)readTVar :: TVar a -> STM awriteTVar :: TVar a -> a -> STM ()atomically :: STM a -> IO aretry :: STM aorElse :: STM a -> STM a -> STM a
instance Monad STM
A Counter Example
update :: TVar Int -> STM ()update counter =
do v <- readTVar counterwriteTVar counter (v+1)
STM means: part of a transaction
34PPHT09 – TM
updateIO :: TVar Int -> IO ()updateIO counter =
do putStrLn ”Before update”atomically (update counter)putStrLn ”After update”
Perform the transaction
A Shared Counterexample?
loop n counter =forM_ [1..n]
(\_ -> atomically (update counter))
main =do counter <- newTVarIO 0
• What is going on?
• Why is this not working?35PPHT09 – TM
do counter < newTVarIO 0forkIO (loop 100000 counter)loop 100000 counterval <- atomically (readTVar counter)putStrLn (show val)
Conditional Synchronisation
• Haskell transactions cannot be executed conditionally,instead
atomic (condition) {statements
}
• Conditions can be programmatically checked and the transaction re-run using◦ Normal conditional statements (if, case, …)
◦ And the retry function
36PPHT09 – TM
7
Conditional Synchronisation
• Function retry◦ Abort the current transaction
◦ Re-run when appropriate
• When should a transaction re-run?◦ Recall: each transaction has a log of read variables
◦ Re-run only if a variable in the log has changed
37PPHT09 – TM
Conditional Synchronisation
newRWController = donrVar <- newTVar 0nwVar <- newTVar 0return (nrVar,nwVar)
startRead (nrVar,nwVar) = do dTV V
38PPHT09 – TM
nr <- readTVar nrVarnw <- readTVar nwVarif nw == 0
then writeTVar nrVar (nr+1)else retry
endRead (nrVar,nwVar) = donr <- readTVar nrVarwriteTVar nrVar (nr-1)
Conditional Synchronisation
startWrite (nrVar,nwVar) = donr <- readTVar nrVarnw <- readTVar nwVarif (nr == 0 && nw == 0)
then writeTVar nwVar (nw+1)else retry
• Not quite one slide solution but still quite readable in plain English
39PPHT09 – TM
else retry
endWrite (nvVar,nwVar) = donw <- readTVar nwVarwriteTVar nwVar (nw-1)
Controlling Fairness
• Previously we studied examples of how to explicitly control the fairness policies◦ Starving out writers?
◦ Servicing in the order of arrival, etc.
• This cannot be easily done in the Haskell transactional setting◦ All processes blocking on a particular variable are
woken up when the variable is modified
◦ It is up to the scheduler to ensure fairness
40PPHT09 – TM
Unbounded Buffers
• Producer can work freely
• Consumer must wait for producer
41PPHT09 – TM
Infinite bar
… …
Unbounded Buffer
newBuffer = newTVarIO []
put buffer item = dols <- readTVar bufferwriteTVar buffer (ls ++ [item])
42PPHT09 – TM
get buffer = dols <- readTVar buffercase ls of
[] -> retry(item:rest) -> do
writeTVar buffer restreturn item
8
A Shared Counter Example
• We need to wait until all the threads stop◦ Use a buffer as a channel for termination messages
main =do counter <- newTVarIO 0
j i B ff
43PPHT09 – TM
join <- newBufferforkIO ((loop 100000 counter)
`finally`atomically (put join ()))
loop 100000 counteratomically (get join)val <- atomically (readTVar counter)putStrLn (show val)
Laziness Issues
• Lazy evaluation means expressions are only evaluated when their result is needed◦ Stores unevaluated expressions
◦ Possibly high memory use
◦ The shared counter example can overflow stack
44PPHT09 – TM
update counter =do v <- readTVar counter
writeTVar counter (v+1)
Laziness Issues
• Strictly evaluating expressions to be stored in a shared variable◦ Only values are stored
◦ Constant space
◦ A small change to the program is necessary:
45PPHT09 – TM
update counter =do v <- readTVar counter
writeTVar counter $! (v+1)
Compositionality
• (Sequentially) composing transactions is embarrassingly simple:
transfer buffer1 buffer2 = doitem <- get buffer1
• Transactions can be freely composed◦ They are only executed (“locked-down”) with the atomically call
46PPHT09 – TM
item < get buffer1put buffer2 item
Composing Alternatives
• The workings of orElse◦ Take two transactions
◦ Execute the first one, and if it succeed commit
◦ If the first one retries execute the second one
• Can be used to listen to several channels (buffers) at once◦ Similar to the JR input statement
47PPHT09 – TM
getEither buffer1 buffer2 =get buffer1 `orElse` get buffer2
Composing Alternatives
• retry allows to construct blocking functions
• orElse provides an elegant way to construct corresponding non-blocking functions
48PPHT09 – TM
tryGet :: Buffer a -> STM (Maybe a)tryGet buffer =
do item <- get bufferreturn (Just item)
`orElse`return Nothing
9
Transactions — Benefits
• Many processes can be in the critical section at the same time◦ Potentially more parallelism
◦ They only need to re-run if there is an actual i fliruntime conflict
• Deadlocks cannot occur
• Easy to compose — at least in Haskell◦ Compose as you like
◦ Commit is only done when we run the transaction
49PPHT09 – TM
Transactions — Drawbacks
• Hard to guarantee fairness◦ A large transaction can be starved by many small
transactions
• All the (log) book keeping can be expensive
• Possibly poor performance in certain race-heavy work loads◦ But this is implementation dependent
• Side effects must be well controlled◦ An expressive type system can be an advantage
50PPHT09 – TM
Transactional Memory — Summary
• Advantages◦ Conceptually simple
◦ Expressivness
◦ No deadlocks
◦ Compositionality• In some variants as for example Haskell
• Question marks◦ Fairness
◦ Performance
◦ I/O51PPHT09 – TM
Next Week
• Monday◦ Urban Boquist from Ericsson:
Industrial use of Erlang
• Wednesday◦ What to expect on the exam
• Example questions
• Course overview — what you should know by now
52PPHT09 – TM