Lecture 7
Review -- key points on semantics of concurrent programs
assertions -- predicates that are true at points in a program
interference -- statements in one process invalidating assertions in another
avoiding interference
disjoint variables -- independent parts
weakened assertions \
global invariants | overlapping parts (process interaction)
synchronization /
Safety and Liveness Properties (Section 2.8)
safety -- nothing bad ever happens (=> no bad states)
liveness -- something good eventually happens (=> progress)
Producer/Consumer problem revisited -- proof outline in Figure 2.4
mutual exclusion (p==c and p>c) == false
this is an instance of a general method called
Exclusion of Configurations
progress -- assuming await statements get a chance
proving liveness properties requires a stronger logic (e.g., temporal)
The Critical Section Problem (Section 3.1)
[the CS problem, or is it even "the" CS (Computer Science) problem?]
what? implementing (often large) atomic actions in software
why? linked lists in OSs, database records, counters (homework 1b), etc.
example -- ready list in an OS (or the SR RTS)
diagram on board showing structure
new process -- add descriptor to tail of list
free processor -- remove descriptor from front of list
add and remove have to be atomic. Why? (they manipulate two links)
implementing atomic actions
machine instructions basic building block
disable interrupts kernel of an OS on a processor
spin locks (busy waiting) multiprocessor OS or parallel program
blocking primitives higher-level parts of an OS or
(e.g., semaphores) multithreaded programs
we are going to see now how to implement spin locks; later we will
look at blocking primitives.
Model for CS Problem
process CS[i = 1 to n] {
while (true) {
CSenter: entry protocol;
critical section;
CSexit: exit protocol;
noncritical section;
}
}
specifying mutual exclusion
int in[1:n] # initially all zero
in[i] = 1 when process i is in its critical section
at all times require
MUTEX: 0 <= sum of in[i] <= 1
add assignments to in[i] to the above code outline and use
an await statement to ensure the MUTEX is a global invariant
how to implement << await (sum in[i] == 0) in[i] = 1; >> in above program
change variables; let mutex = 1 - (sum in[i])
initially mutex == 1; at all times want 0 <= mutex <= 1
use this change of variables to modify the above code, getting
<< await(mutex == 1) mutex--; >>
critical section;
<< mutex++ >>
mutex is a semaphore and these statements are the P() and V() operations
(See Chapter 4 in the text book and Chapter 8 in the SR book)
[Note: I introduced this simple use of semaphores now so they could
use it in their programming assignment.]
Spin Locks (Section 3.2)
semaphores are special primitives; nontrivial implementation (see Chap 6)
how can we solve the CS problem using machine instructions directly?
observation -- there are only 2 key states:
nobody is in its CS -- lock == false
somebody is in its CS -- lock == true
Using just lock, we get the following code:
<< await (!lock) lock = true; >>
critical section
lock = false; # angle brackets not needed. why?
Figure 3.2
This still uses an await statement, but it is very simple
All modern machines provide some way to implement this kind of spin lock
Test and Set
the first instruction for implementing spin locks (IBM, mid 1960s)
bool TS(bool lock) { # an atomic instruction
<< bool initial = lock;
lock = true;
return initial; >>
using TS, we get the following simple solution
CSenter: while (TS(lock)) skip;
CSexit: lock = false # simply reinitialize
explain why this is called a spin lock
brief explanation of properties (details next time):
mutual exclusion
absence of unnecessary delay (with weakly fair scheduling)
absence of livelock (deadlock) (with weakly fair scheduling)
but not fair -- no GUARANTEE of eventual entry