1.49k likes | 1.72k Views
Concurrent Programming (part of CSc213/4). Geoff Coulson Distributed Multimedia Research Group, Department of Computing, Lancaster University geoff@comp.lancs.ac.uk. Administrative details. Geoff Coulson room C2, Computing Building phone 93054 email: geoff@comp.lancs.ac.uk
E N D
Concurrent Programming (part of CSc213/4) Geoff Coulson Distributed Multimedia Research Group, Department of Computing, Lancaster University geoff@comp.lancs.ac.uk
Administrative details • Geoff Coulson • room C2, Computing Building • phone 93054 • email: geoff@comp.lancs.ac.uk • web: http://www.comp.lancs.ac.uk/computing/staff/geoff/ • the course • 1 lecture per week for 5 weeks concurrently (sic) with Prof. Hutchison’s Computer Architecture course • then 3 lectures in week 6 • after this, 12 lectures (3 lectures per week) on Operating Systems take us to the end of the term and CSc213/4
Coursework • coursework on web • programming exercises in Java • workflow problem • uses semaphores • public bathroom problem • uses native Java concurrency primitives
Unit 1: Introduction to concurrentprogramming • aims • to define the concept of a process • to distinguish concurrency and parallelism • to introduce concurrent programming • process management • concurrency management • to outline the rest of the course
Three important concepts • processor • the hardware device which executes machine instructions • program • a sequence of instructions awaiting execution • a completely static description of what you would like to happen • process • a program in execution on a processor • an active entity in the system which executes the algorithm defined by a program
Why a process is not a program • a program can be executed by multiple processes at the same time • me running xemacs is a different process from you running xemacs! • one program, two processes • a process can run one program and then another • the pre-processor of a compiler may be a separate program (i.e. executable file) from the syntax analyser, but they may be executed one after the other by the same process
Processes abstract over... • processors • can have many processes regardless of number of processors available • interrupt processing • handling external events like keypresses or clock ticks • context switches • jumps of execution to another part of memory (more detail later) sequential execution processes + interrupts
Concurrency versus parallelism • processes are always concurrent but not always parallel • parallelism means that more than one process is executing at any given instant => more than one processor • concurrency means that more than one process can be underway simultaneously; but maybe that only one is executing at any given instant contextswitches (performedat a timechosen by the scheduler) P1 P2 P1 P2
What is concurrent programming? • writing programs that involve more than one process • examples • implementing operating system internals • exploiting multiprocessor machines to make algorithms run faster • structuring inherently concurrent applications • an X server must listen for keypresses, mouse clicks and draw requests from programs “at the same time” • an animation applet must do the animation, listen for the command to stop, speed up etc. “at the same time” • an aircraft control system must respond to joystick, throttle and wind direction sensors “at the same time”
Event driven programming • programmed either concurrently or sequentially • timeliness/ scheduling issues • structuring issues waitfor(keypress); waitfor(mouse); waitfor(draw_req); waitfor(event); if (event==keypress) ...else if (event==mouse) ......else if (event==draw_req)...
What does concurrent programming involve? • process management • involves single processes • creating, suspending, destroying, ... • concurrency management • involves the coordination/ synchronisation of multiple processes • competition (e.g. for a variable that two processes both want to update) • cooperation (e.g. when one process wants to tell another that a needed result is available)
The need to manage competition 1 • scenario: a bank account object is shared by multiple processes public class Bank_account {private int bal = 0;public void Bank_account(int start_balance) { bal = start_balance;}public void update(int amount) { bal = bal + amount;} } ... Bank_account b = new Bank_account(0);
The need to manage competition 2 • two processes call b.update(5) at around the same time • finalresult may be that balance is 5 rather than 10! process 1: update(5);process 2: update(5); read bal into CPU (0) add 5 to it (5) read bal into CPU (0) write 5 back to bal variable add 5 to it (5) write 5 back to bal
The need to manage cooperation 1 • scenario • process 1 reads a value from the keyboard and places it in a Dimension object • process 2 extracts the value from the Dimension object and draws a box of the given dimensions public class Dimension {private int dim = 0;public void put(int d) { dim = d;}public int get() { return dim;} } Dimension d = new Dimension();
The need to manage cooperation 2 • process 2 runs before process 1 has set d and thus draws a nonsensical box Process 1Process 2 size = Basicio.readInteger(); boxsize = d.get(); d.put(size); box.draw(boxsize);
Outline of rest of course #1 • unit 2: process management • how single processes are managed in Java and UNIX • unit 3: concurrency management: competition 1 • mechanisms and abstractions for mutual exclusion • Java primitives • spin locks • unit 4: concurrency management: competition 2 • mechanisms and abstractions for mutual exclusion • semaphores
Outline of rest of course #2 • unit 5: concurrency management: cooperation 1 • mechanisms and abstractions for condition synchronisation • Java primitives • semaphores (again) • unit 6: concurrency management: cooperation 2 • other concurrency management schemes • unit 7: examples in Java • unit 8: example in UNIX/C
Books • You shouldn’t need to buy one but the following are worth a look: • Lea, D., “Concurrent Programming in Java”, Addison Wesley, 1997, ISBN 0-201-69581-2 • Burns, A., and Davies, G., Concurrent Programming, Addison Wesley, 1993, ISBN 0-201-54417-2 • Switzer, R., "Operating Systems: A Practical Approach", Prentice Hall, 1993, ISBN 0-13-640152-X
Summary • a program is not a process is not a processor • processes abstract over processors, interrupt handling and context switches • concurrency is not the same as parallelism • concurrent programming is programming with more than one process • concurrent programming involves • process management • concurrency management • problem: competition => solution: mutual exclusion • problem: cooperation => solution: condition synchronisation
Unit 2: Process management • aims • to see how processes are managed in Java • processes are called threads in Java • to see how processes are managed in UNIX/C • to compare and contrast the above two notions of process
Java threads • consider a MessagePrinter class with a method print_string() that prints a string in a text window • in a conventional program the main program must wait “a long time” for the output to complete before being able to continue main program message printer
Using a thread • instead, we could use a thread in the MessagePrinter object to allow the main program to continue “immediately” main program message printer concurrency
The “main program” code... /* create a MessagePrinter for the message */ MessagePrinter mp = new MessagePrinter(“Hello sailor”); /* create a new thread for the MessagePrinter */ Thread t = new Thread(mp); /* start the thread */ t.start(); /* ...now do other stuff while the message prints */
Interfaces and “Runnable” (...before we see the MessagePrinter class...) • to create a thread in Java • define a class that implements thejava.lang.Runnable interface • create an object of class Thread • pass an object of the above class to the Thread constructor • n.b. an interface is like a class with method signatures but no method implementations • “implementing” an interface is like extending a class • but, in addition, we must provide implementations of all the method signatures in the specified interface • in Runnable there’s only one signature - public void run()
The MessagePrinter class public class MesssagePrinter implements Runnable {String message;public MessagePrinter(String m) { message = m; }public void run(){ TextArea text = new TextArea(...); text.appendText(message);} }
Summary: the three steps to thread creation 1. Define a class that implements the Runnable interface (i.e. it has “implements Runnable” in the class header and contains a run() method) 2. Pass the above to the constructor of the Thread class 3. Call the start() method of the newly created Thread object -- this causes Java to immediately exececute yourrun() method as a new thread
Some other methods in class Thread • we have seen start() but there are many others... • suspend(), resume() - temporarily pause a thread • stop() - irrevocably stop the thread • sleep() - suspend the thread for a given time • isAlive() - return true iff the thread is started and not yet terminated • join() - suspend calling thread until target completes • Thread.currentThread() - a static class method that allows the calling thread to determine its identifier
Alternative ways to create Java threads • extend the Thread class • implement run() inside the extended class • to create: new EntendedThread().start(); • implement Runnable but hide thread creation inside the new class • constructor of new class contains: new Thread(this).start();
UNIX Processes (n.b. these are not method calls on an object!) • fork() - create ‘child’ process just like the caller • wait() - block until a child process dies • execve() - make calling process run a new program • kill() - send a signal to a process • signal() - set a routine to be called when a signal arrives • pause() - block until a signal arrives • exit() - commit suicide • getpid() - get process identifier of calling process • brk() - set size of caller’s data segment
The fork() call is bizarre... • fork() creates an (almost) identicalcopy (child) of the calling process (parent) • has copies(!!) of the same variables with same values as parent • has same program as parent • just like the parent, the child resumes execution at the line following the fork() call • only difference is the value returned from the fork() call • in the child this is 0 • in the parent this is a value greater than 0 (the actual value returned is the process identifier of the child)
An example /* file: prog.c */ /* file: main.c */ main() main() { { (SIGINT, trap); int pid, status; signal pause (); if ((pid fork ()) < 0) { = exit (0); /* handle error */ } } else if (pid == 0) { execve ("prog","",0); trap() /*NOTREACHED*/ { printf("%d: got it!\n", } else { ()); getpid kill (pid, SIGINT); pid = wait (&status); } } }
init login login login shell shell shell ls cat The UNIX process hierarchy • every process in UNIX (except one) has a parent • processes may create many children (via fork()) • example: the UNIX boot procedure • initially, a single parentless process called init runs • init reads a file which logs the connected terminals • init forks a login process for each terminal • if a login process validates a user it forks a shell process
/* shell */ while(TRUE) { read_command(command, parms); if (fork() == 0) { execve(command, parms, 0); } else { wait(&status); } } The UNIX shell
Comparing Java threads and UNIX processes • Java threads • lightweight (useful for fine grained concurrency) • can share common memory, i.e. common objects (useful for cooperation) • are user level (efficient and system independent) • UNIX processes • heavyweight • cannot share memory (good for isolating activities of separate users) • system level
Summary • to create a thread in Java we create an object of a class that implements the Runnable interface and pass it to the Thread constructor • the Thread class has methods for process management • UNIX processes are created through the fork() call and there are other calls for process management • UNIX processes are very different from Java threads
Unit 3: Concurrency management: competition 1 • aims • to demonstrate how Java controls competition by providing mutual exclusion over critical sections • to begin an exploration of locks which are the foundation on which mutual exclusion is built • to understand some common pitfalls • failed mutual exclusion • deadlock • livelock • unfairness
The Bank_account class revisited public class Bank_account {private int bal = 0;public void update(int a) {bal = bal + a;} } process 1: update(5);process 2: update(5); read bal into CPU (0) add 5 to it (5) read bal into CPU (0) write 5 back to bal variable add 5 to it (5) write 5 back to bal
Towards a solution... • the heart of the problem is that multiple concurrent processes can be executing inside update() simultaneously • we can fix it by only allowing one process at a time to be inside update() • deem update() to be a critical section • implement mutual exclusion over the critical section -- i.e. prevent multiple threads from entering it • do this by meams of a lock • the parable of the bronze statue...
Mutual exclusion and locks multiple processes in Bank_account, theshared resource is the bal instance variable { get_lock() lock provides mutual exclusion critical section shared resource release_lock()
The solution in Java • each object has an (invisible) lock • the lock is obtained via the synchronized statement synchronized(<object for which lock is needed>) {<arbitrary block of code to run with lock held>} • the synchronized statement blocks entry to the code block until the lock can be claimed (cf. get_lock()) • the lock is released after the synchronized statement so another thread can enter (cf. release_lock()) • synchronized can also be applied to a whole method public void synchronized update(int a) { bal = bal + a; }
A safe bank account public class Bank_account {private int bal = 0;public void Bank_account(int start_balance) { bal = start_balance;}public void synchronized update(int amount) { bal = bal + amount;} }
The new Bank_account in action • Java arranges to call get_lock() at start of synchronized method and release_lock() at end process 1: update(5);process 2: update(5); <get_lock() succeeds> read bal into CPU (0) <get_lock() fails> add 5 to it (5) write 5 back to bal variable <release_lock()> <get_lock() succeeds> read bal into CPU (0) add 5 to it (5) write 5 back to bal <release_lock()>
Implementing locks (n.b. Java programmers don’t actually care how locks are implemented!) • there are two main types of lock • spin locks (this unit...) • get_lock()spins until the lock is available; it returns eventually having obtained the lock • spinning means continually executing a while loop that only drops out when the lock has been obtained • blocking locks or semaphores (next unit....) • get_lock()blocks until the lock is available; it returns eventually having obtained the lock • blocking means that the scheduler doesn’t consider the blocked thread for execution until it can be given the lock • Java uses blocking locks but these, in turn, use spin locks (see later...)
A (flawed) spin lock implementation public class Lock { /* pseudo-java */ int lk = 0; public void get_lock() { while(lk == 1) /* do nothing (i.e. spin) */; lk = 1; /* claim the lock */ } public void release_lock() { lk = 0; /* let someone else claim lock */ } } • problem: competition for the lk variable itself! • thread A reads lk as 0 • a context switch occurs before A can claim the lock (i.e. set lk to 1); thread B is scheduled, runs, and claims the lock; • when A is resumed, it carries on and claims the lock as well!
Solutions? • in Java we would fix this by making get_lock() a critical section using synchronize -- but here we are trying to provide locks with which synchronize can be implemented! • three solutions are • disable interrupts • use a special atomic machine instruction • use a software only solution - Peterson’s algorithm
Disabling interrupts • a context switch can only happen when there is an interrupt (see later in OS course) • so, to prevent competition on lk, we disable interrupts while it is being accessed (i.e. we “lock the lock”) public void get_lock() { /* pseudo-java */ try_again: disable_interrupts(); if (lk == 1) { /* lock is taken */ /* permit context switch */reenable_interrupts(); goto try_again; /* spin */ } lk = 1; /* claim lock */reenable_interrupts(); } public void release_lock() { lk = 0;}
Disabling interrupts #2 • an alternative spin lock based on disabling interrupts: public void get_lock() { disable_interrupts();} public void release_lock() { reenable_interrupts(); } • comparative advantage is simplicity + fewer calls of disable_interrupts() and reeneable_ interrupts() • perhaps less overhead • disadvantage is error proneness and the fact that interrupts may be disabled for longer than necessary • clock ticks, IO events etc could be missed
Using a special machine instruction • e.g. test-and-set • int TST(x, val); - in one atomic action, return the old value of variable xand update its value to be val • “atomic action” - an interrupt may come before or after the instruction but not while it is in execution public void get_lock() { /* pseudo-java */ while(TST(lk, 1)) /* spin */; } public void release_lock() { lk = 0;} • other examples are compare-and-swap, fetch-and-add, ...
Peterson’s algorithm • “clever” algorithm; no hardware support needed • two thread version (assumes thread ids of 0 and 1) private int turn = 0; /* new inst. variables */ private bool[] req = {FALSE, FALSE}; public void get_lock() { int pid = Thread.currentThread();int other = 1 - pid; req[pid] = TRUE; turn = other; while(turn == other && req[other]) /* spin */; } public void release_lock() { req[Thread.currentThread()] = FALSE; }
Pitfalls in concurrent programming • the following are the most common pitfalls • failed mutual exclusion • deadlock • livelock • unfairness • we will understand these by examining a series of flawed but plausible approximations to Peterson’s algorithm... • in the process we will better understand how Peterson’s algorithm works