Title: Concurrent Programming Abstraction
1Section 2
- Concurrent Programming Abstraction Java Threads
22.1 Concurrent Programming Abstraction
- The execution of a concurrent program consists of
multiple processes active at the same time. Each
process is the execution of a sequential program. - If the computer has multiple PEs then there is
parallel or real concurrent execution. - However, with a single PE the processor is
switched among the processes. This interleaving
is referred to as pseudo-concurrent execution.
32.1 Concurrent Programming Abstraction
- Without loss of generality, we will always model
concurrent execution as interleaved whether or
not implementations run on multiple PEs.
l1
Sequences are interleaved.
l2
l1
l2
42.1 Concurrent Programming Abstraction
- In most systems absolute time is unimportant.
- Systems are frequently upgraded with faster
components. Correctness should not depend on
absolute time. - Time is ignored only sequences are important.
Arbitrary interleaving is allowed. Program must
be correct under all interleavings.
52.1 Concurrent Programming Abstraction
- In our abstraction, whether on multiple PEs or a
single PE, each process is considered to be
operating on its own processor, executing its own
program. Only two possible interactions to
consider - contention - competing for a shared resource
- communication - synchronise (agree that a
certain event has to take place).
62.1 Concurrent Programming Abstraction
Concurrent programming abstraction is the study
of interleaved execution of the atomic
instructions of sequential processes.
- The absolute time taken by an atomic instruction
is ignored. - Arbitrary interleavings are allowed.
- The interleaving must be fair i.e. no process is
deferred forever. - A concurrent program is required to be correct
under all fair interleavings. Note Conventional
debugging to detect and correct errors is
impossible
72.1.1 Interleaving
Consider the following program
What are the possible interleavings ? (an
interleaving is sometimes referred to as a
history or trace)
p1a p1b p2a p2b p2a p2b p1a p1b p1a p2a
p1b p2b p2a p1a p2b p1b p1a p2a p2b
p1b p2a p1a p1b p2b
82.1.1 Interleaving
The number of interleavings in a concurrent
program is generally enormous. Suppose a
concurrent program contains n processes and that
each executes a sequence of m atomic actions. The
number of different interleavings is
Three processes each having two atomic actions
will result in 90 possible interleavings.
92.1.1 Interleaving
Consider the following program
What is the meaning of this program? That is,
what are the values of c1 and c2 after execution
of the program?
Two possible answers c1 9 c2 3 if p1
executes before p2 c1 15 c2 3 if p2
executes before p1
102.1.1 Interleaving
Consider the following program
process P1 p1 continue false end P1
When there is contention among processes a
scheduling policy determines which will execute
next. Suppose a scheduling policy assigns a
processor to a process until that process either
terminates or delays? If there is only one
processor, is the scheduler fair ?
112.1.2 Atomic instructions
It is extremely important to define exactly which
instructions are being interleaved i.e. which
instructions are atomic.
An atomic action makes an indivisible state
transition.
Consider the following simple program which is
being executed on a 2-processor computer. Each PE
has its own set of registers.
122.1.2 Atomic instructions
If the compiler translates nn1 into a single
INC machine language instruction, any
interleaving will give the correct result.
132.1.2 Atomic instructions
However, if the computation is performed using a
load-store architecture some interleavings will
give an incorrect result. In a load-store
architecture values are manipulated by loading
them into registers, operating on them and then
storing the results back into memory.
142.1.2 Atomic instructions
The previous example illustrates the lost update
problem. This is an example of a race condition.
Race conditions occur when two or more processes
share data and the final result, which may be
erroneous, depends on the interleaving of the
processes atomic actions.
Problem A bank has 10,000 accounts. Each account
has exactly 1,000. Periodically, an ATM process
picks two accounts at random and moves a random
amount of money from one to the other.
Periodically, an Auditor process totals the banks
assets to check for embezzlement of funds. What
might happen ?
152.1.2 Atomic instructions
Assuming a load-store architecture
i.e. Instruction 1a is implemented as Load y
into R Add z to R Store R into x what are the
possible final values of x in the following
program ?
int y0 int z0
process P1 int x 1a xyz end P1
process P2 2a y1 2b z2 end P2
162.1.2 Atomic instructions
2a2b1a1b1c 2a1a2b1b1c 2a1a1b2b1c 2a1a
1b1c2b 1a2a2b1b1c 1a2a1b2b1c 1a2a1b1
c2b 1a1b2a2b1c 1a1b2a1c2b 1a1b1c2a2b
int y0 int z0
process P1 int x 1a Ry 1b RRz 1c
xR end P1
process P2 2a y1 2b z2 end P2
We could enumerate all 10 interleavings and
evaluate x at the end of each. Alternatively we
could try and reason about the program.
172.1.2 Atomic instructions
int y0 int z0
The shared variable y is only modified in one
place in P2 (2a) and only used in one place in P1
(1a). We must therefore consider two
cases 2a..1a and 1a..2a The shared variable z is
only modified in one place in P2 (2b) and only
used in one place in P1 (1b). Therefore, within
the cases above we must also consider 2b..1b and
1b..2b
process P1 int x 1a Ry 1b RRz 1c
xR end P1
process P2 2a y1 2b z2 end P2
lt-2b-gt 2a...1a1b x3 (2 interleavings)
2a...1a1b..2b x1 (2 interleavings)
lt-1b-gt 1a...2a2b x0 (? interleavings)
1a...2a2b..1b x2 (1 interleaving)
182.1.3 Correctness
Partial correctness and total correctness are
appropriate for concurrent programs which
terminate just as they are for sequential
programs.
- Partial correctness a program is partially
correct if the final state is correct assuming
the program terminates. - Total correctness a program is totally correct
if it always terminates with a correct answer.
- In addition, the correctness of concurrent
programs is defined in terms of properties of
execution sequences. There are two types of
correctness properties - safety properties
- liveness properties.
192.1.3 Correctness
- Safety properties the property must always be
true (nothing bad will ever happen). - Mutual exclusion processes may not interleave
certain sub-sequences of instructions e.g. at
most one process is permitted to access a printer
at any one instance. - Absence of deadlock deadlock occurs when all
processes are blocked and are unable to proceed. - Liveness properties the property must eventually
be true (something good will eventually happen). - Absence of starvation e.g. if a process posts a
request to print, eventually it will be assigned
a printer.
202.2 Java Threads
In Java a process is represented by a thread. To
make a thread run you call its start() method.
This registers the thread with the thread
scheduler. The scheduler, which may be part of
the JVM or the host operating system, determines
which thread is actually running in the CPU at
any given time. Calling the start() method does
not cause the thread to run immediately it only
makes it eligible to run. The thread must contend
with other threads for the CPU. When a thread
gets to execute, it executes a run() method
- either its own run() method
- or the run() method of a some other object.
212.2.1 Executing a Thread
class Interleave public static int c1 2
public static int c2 3 public static void
main (String args) Thread p1 new P1 ()
Thread p2 new P2 () p1.start () p2.start
()
class P1 extends Thread public void run ()
Interleave.c1 Interleave.c1 Interleave
.c2 class P2 extends Thread public void
run () Interleave.c1 Interleave.c1
Interleave.c2
Extend the Thread class and override its run()
method.
222.2.1 Executing a Thread
class Interleave public static int c1 2
public static int c2 3 public static void
main (String args) Thread p1 new
Thread(new P1()) Thread p2 new Thread(new
P2()) p1.start () p2.start ()
class P1 implements Runnable public void run
() Interleave.c1 Interleave.c1
Interleave .c2 class P2 implements
Runnable public void run ()
Interleave.c1 Interleave.c1
Interleave.c2
Sometimes it is desirable to implement the run()
method in a class not derived from Thread but
from the interface Runnable.
232.2.1 Executing a Thread
When the run() method ends the thread is
considered dead. A dead thread cannot be started
again, but it still exists and, like any other
object, its methods and data can still be
accessed.
- A dead thread cannot be restarted.
- A dead threads methods can be called.
242.2.2 Thread States
When a threads start() method is called, the
thread goes into a ready-to-run state and stays
there until the scheduler moves it to the running
state. In the course of execution the thread may
temporarily give up the CPU and enter some other
state.
252.2.2 Thread states - Yielding
A thread can offer to move out of the CUP by
yielding. A call to the static yield() method
causes the currently executing thread to move to
the Ready state. If there are any other threads
in the Ready state, the thread that just yielded
may have to wait before it gets to execute again.
If there are no waiting threads in the Ready
state the thread that just yielded will get to
continue executing immediately.
262.2.2 Thread states - Sleeping
A call to the static sleep() method requests the
currently executing thread to cease executing for
an approximately specified period in milliseconds.
Note when the thread finishes sleeping (20
milliseconds) it does not continue execution
directly.
The Thread class has an interrupt() method. A
sleeping thread that receives an interrupt() call
moves immediately into the Ready state when it
gets to run it will execute its
InterruptedException handler.
272.2.2 Thread states - Blocking
If a method needs to wait for an indeterminable
amount of time until some I/O occurrence takes
place it should step out of the Running state.
This is know as blocking. All Java I/O methods
behave this way.
A thread can also become blocked if it fails to
acquire the lock for a monitor or if it issues a
wait() call. This will be explained later.
282.2.2 Thread states - Suspending, Resuming and
Stopping
The suspend() method allows any arbitrary thread
to make another thread un-runnable for an
indefinite period of time. The suspended thread
becomes runnable when some other thread resumes
it using the resume() method. The stop() method
allows any arbitrary thread to kill another
thread.
These three methods are now deprecated. They
should be avoided as they are unsafe and can
easily lead to deadlock.
292.2.3 Thread Priorities and Scheduling
Every thread has a priority (from 1..10). All
newly created threads have their priority set to
that of the creating thread. Higher priority
threads get preference over lower priority
threads. The scheduler generally chooses the the
highest-priority waiting thread. If there is more
than one waiting thread the scheduler chooses one
of them there is no guarantee that the one
chosen is the one that has been waiting longest.
302.2.3 Thread Priorities and Scheduling
Historically, two approaches have emerged for
implementing thread schedulers.
- Preemptive scheduling (Solaris, Windows pre
JDK1.0.2) - A thread runs until - it leaves the Running state by yielding,
blocking, sleeping etc - it gives way to a higher-priority thread.
- Time-sliced (Mac, Windows with JDK 1.0.2) - A
thread is allowed to execute for a limited amount
of time (approximately 100 milliseconds). It is
then moved into the Ready state where it must
contend with all other ready threads.