% to get hardcopy of this lecture, you need the following files (plus the
% normal latex and tex base directories):
%     lecture9.tex:     latex source file
%     macros.tex:      figure macros
%     psfig.tex:       postscript figure (psfig) macro definition
%     sequent.idraw:      first figure, created with idraw
%     conc.idraw:  second figure, created with idraw
%     pocs-header.sty: Principles of Computer Systems lecture latex header
%
% run the following programs:
% latex lecture9
%   -> Creates lecture9.dvi, plus latex intermediate files
% latex lecture9
%   -> Run a second time to get cross-references right
% dvi2ps lecture9 > lecture9.ps
%   -> Merges the figures with the text, can also pipe to lpr
%
\documentstyle[12pt,pocs-header]{article}
\Scribe{Carl Heinzl}
\Lecturer{Bill Weihl}
\LectureNumber{9}
\LectureDate{October 9, 1991}
\include{macros} % used to include figures in this document
\begin{document}
\MakeScribeTop

\section{Administrivia}

\begin{table}[h]
\centering
\begin{tabular}{|c|l|} \hline
\multicolumn{1}{|c|}{Handout} &
\multicolumn{1}{c|}{Title} \\ \hline

19 & Proving Concurrent Modules Correct  \\
20 & Problem Set \#4 \\
21 & Solutions to Problem Set \#1 \\
22 & Problem Set \#3 and Solutions from Fall 1990 \\
\hline
\end{tabular}
\end{table}

\section{Overview of Today's Topic: Concurrency}

This lecture will cover the topic of concurrent modules and systems.
The following areas will be discussed:

\begin{itemize}
\item {Sequential State Transitions}
\item{Concurrent System Transitions}
\item{Concurrent SPECS and Implementations}
\item{Abstraction Functions and Rep Invariants for Concurrent Systems}
\item{Atomic and Concurrent Semantics of Spec}
\item{Examples}
\item{Condition Variables}
\item{Correctness}
\end{itemize}

Fundamentally, specifications and implementations are the same for 
concurrent systems as for sequential systems with the addition of 
{\it state}.  We will view these modules as operating using a state 
machine that proceeds along taking state transition.  Handouts
15 and 19 are referenced and form the basis for much of the
material presented here.

\section{Sequential Case Transitions}

In the sequential case, entire procedures are called
that cause state transitions.  Some external module will
call a procedure that will execute sequentially, return
a value and complete the state transition.  Whether these 
procedures are ``atomic'' does not matter in the sequential 
case since once entered, a procedure must run until it 
relinquishes control (i.e., until completion).

The sequential case is equivalent to having atomicity
brackets around the entire body of every procedure, while
the concurrent case allows a finer grain of control.
It is important to note that a transition of a module is
defined as any one of the atomic transitions of that
particular module.

\section{Concurrent System Transitions}

Now, in the concurrent case the possible state transitions of the
state machine are all of the atomic transitions that the 
module can make rather than the transitions of each 
entire procedure (viewed as a single transition).
In real life, procedures consist of several transitions.

There are two types of transitions that we can take;

\begin{description}
\item[internal transition]
Internal transitions have no effect on the state of the specification.
The abstraction function maps them into a null transition.
\item[external transition]
An external transition in the implementation is a single transition that
corresponds directly to a single transition in the specification. Examples
of external transitions are procedure invocations and returns.
\end{description}

In the sequential case there were only
external transitions and anything that happened internally 
was part of some external invocation of a procedure in this
module.  In the concurrent case the external transitions 
 correspond to the invocations and responses of the external
procedures.  The internal transitions are all of the other
transitions that take place internal to these procedures.
Some of these transitions may be the result of FORKing additional 
threads of control that may perform work in the background
in parallel with the external operations. These background
threads are the source of additional internal transitions.
We will see examples of background threads later in the term. 

\section{Concurrent SPECs and Implementations}

The {\bf specification} should only constrain the EXTERNAL 
transitions and sequences of EXTERNAL transitions that the module
can generate.  The internal transitions can be arbitrary 
since they aren't visible outside of the module.  The only data that
is visible outside of the module is the invocation and responses
of the procedures.  This is true under the assumption that the only
communication with the outside world is through the invocation
of the external routines.  If there is shared memory or a
call made to a routine that lives outside of this module, then those
must be viewed as external transitions since it's part of the ``{\it
interface}'' or part of the observable behavior.

The key idea is to understand which of the transitions are the
external transitions, i.e., which transitions are contrained by
the specification, and which transitions appear as internal 
transitions.  The goal is to prove that the implementation generates
sequences of transitions where the subsequence corresponding to the
external transition is allowed by the {\bf SPEC}.

The {\bf SPEC} defines a set of sequences of external transitions.
An example of this is the Mutex Module in the handout that we looked
at the last few times.  For example, one thread invokes Mutex.Acquire and that
moves the {\bf PC} to the beginning of Acquire, then the body of
Acquire does a test--and--set and manages to acquire the mutex so
it returns.  Another thread may then invoke Mutex.Acquire which fails
and so it starts spinning, waiting for the Mutex (held by some
other thread) to be released.  All of these test--and--sets are internal 
transitions.  Eventually, the lock will be released by calling Mutex.Release
which will allow one of the other threads that is spinning in
acquire to take its external transition, complete its work and
return.

So, if we take the sequences of transitions allowed by the spec
or implementation and remove the internal transitions, we are
left with the external transitions specified by the {\bf SPEC}.

The set of possible acceptable behaviors of the module consists of all
the sequences (looking only at the external transitions)
that map to a sequence of transitions allowed by the specification.
That is a fairly abstract view, but more concretely, we can think of
most of these as denoting state machines.  When we have sequential
modules with state we can simply abstract it out to a set of traces,
where the traces are sequences of operation execution, invocation and
response.  Invocations and responses alternate in the sequential case,
but not necessarily so when we're in a concurrent setting.

\section{Abstraction Functions and Rep Invariants for Concurrent Systems}

We still use {\it Abstraction Functions} and {\it Representation Invariants}
to prove correctness, it is just more complicated and subtle.  The 
{\it Rep Invariant} is the same as before, you prove it for the initial 
state and that every transition preserves it, i.e., if
the {\it rep invariant} is true in the state in which the transition
starts and the state in which the transition ends, and it therefore
holds for every {\em reachable}\/ state by induction.

The {\it Abstraction Function} is more complicated.  In the sequential
setting we must show that if the implementation can take us through a
transition (i.e., an invocation can occur and take us through some
initial state, some final state, and some result), then the specification of
of the operation allows that same transition from the abstraction of the
initial state to the abstraction of the final state.

\begin{center}
\begin{verbatim}

         r      (tr)->   r'
         |               |
         v               v
         AF(r)  (tr)->   AF(r')

\end{verbatim}
\end{center}
       
So, whenever we have a transition (that takes us from state r to r')
in the implementation, we must have a corresponding transition in the 
spec.  That shows that every trace of the implementation is a trace of 
the spec.

In the sequential case the transitions are operations and are the
same for the spec and the implementation.  In the concurrent case
we might have more or different transitions in the implementation.
It will, however, always be the case that the external transitions
correspond one--to--one.  In general what we have to show in the
concurrent case for the abstraction function is that the initial 
state abstracts back to the initial state of the spec, then for all 
atomic steps of the implementation there is some sequence of steps
in the spec that does the same thing.  A sequence of steps in the
spec for a given step in the implementation must have the same
sequence of external steps in it.  An implementation may have
more (more likely) or fewer atomic steps to accomplish the same step 
as in the spec. 

The rep invariant in the case of concurrent programs must state 
properties of the states of the variables and of the states of 
various threads (i.e., about their PCs).  In the case of Mutex,  a simple
property (invariant) can state that we must guarantee that there is only
one thread in a critical section of code at one time.  In order
to state this, we must talk about program counters of threads.
This is a fundamental difference between sequential and concurrent
modules.

So, let's assume that AF(tf) is a sequence of transitions of the
spec with the constraint that it has the same {\bf external} transitions
as {\it tr}.  Then we have to show that AF(tr) takes AF(r) to AF(r').
The abstraction function is constrained to preserve exactly the external
transitions, then the subsequence of external transitions in the 
implementation is the same subsequence of transitions allowed by the spec.
%NOTE!!!e - end of 1st tape
It is important, when reading SPEC programs to take close note
of what must happen atomically and what may happen concurrently.

\section{Atomic and Concurrent Semantics of Spec} 

There are several ways to express the primitive atomic and concurrent
operations in SPEC.  The following are primitive atomic operation:

\begin{itemize}
\item Atomicity Brackets - (obviously)
\item Atomic Operations - e.g. Expression Evaluation using functions
\item Guard Evaluation - Guard evaluation is always done atomically, 
regardless of how complicated it is and has the effect of altering 
the {\it PC}
\item Guard plus statement - must be done by adding atomicity brackets
\item Assignment - the actual assignment across the equal sign is performed
atomically.  RHS evaluation may actually take several atomic steps depending
on what calls are made.
\item \verb!VAR x | ...! -  choose some variable ({\tt x}) specifically such that the first
step following the ``\verb!|!'' is done atomically.  This makes sense
since we're introducing a local variable, and no other thread can
alter its value; if we allowed the first statement to block (assuming
it's a guard) it would remain blocked forever!
\end{itemize}
                   
Non-atomic statements may consist of a sequence of transitions and may
therefore block in the middle of a statement.

The FORK call creates another thread that runs the procedure
concurrently.  In order to coordinate the results of concurrently
running procedures, other methods (e.g. using shared memory) must be
devised by the programmer, it is not handled in the SPEC language.
Standard scoping rules are used for the handling of variables within
concurrent modules.

\section{Examples}

\subsection{Mutex Lock}

A very simple implementation of a Mutex Lock would be to repeatedly
test - is it available? - and when it is finally available, atomically
set it so that it's held again (and you have it).  Spinning using this 
method may be fine for a multiprocessor and the amount of time you expect
to spin is relatively small.  If the time to test this is greater than the 
time of a context switch then it would be better to let other threads 
run.  On a uniprocessor, letting other threads run is clearly the better
choice.

\subsection{FIFO Buffer}

Another instance when spinning would be bad is that of the standard
Producer/Consumer relationship in which the producer is enqueing data
into a buffer and the consumers is removing it (e.g. a formatting procedure
such as Latex is writing to a print buffer that is then picked up by
a printer daemon and printed.)  The buffer allows a mismatch of rates between 
the two processes during a brief period of time so they do not have to synchronize
on every character (which could be very ineffecient).  The producer may fill the 
buffer and the consumer will empty it when it can.  Once the buffer is full, 
there is no sense allowing the producer to continue wasting time in the same vein 
as there is no reason to allow the consumer (printer process in this case) wait
since it may be a long time before any data is entered into the buffer.  What we 
want is an operation on the buffer that effectively says ``give me something from 
the buffer and if nothing is available put me aside and make me wait (i.e.,
don't return) until there's something available''.  This is known as a
condition variable and is treated in depth in the next subsection.  We will
also return to the FIFO later.

\section{Condition Variables}

For each process that needs to wait in a given module, there is some
condition that it needs to wait for (e.g. the printer needs to wait for
the buffer to be not empty and the formatter needs to wait for the
buffer to be not full).  The three procedures (actually four when
the ``create'' routine is included) that are necessary to support
condition variables are:

\begin{itemize}
\item Create
\item Wait
\item Signal
\item Broadcast
\end{itemize}

Condition variables are used in conjunction with Mutex locks.  The Mutex
must be used to assure integrety of the data being enqueued, dequeued,
or tested.  So the two simple scenarios are:

\begin{itemize}
\item Acquire Mutex
\item Perform Test
\item If true - perform work
\item Release Mutex and Return
\end{itemize}

\begin{itemize}
\item Acquire Mutex
\item Perform Test
\item If not true - release Mutex and Wait
\item Perform work
\item Release Mutex and Return
\end{itemize}

The Mutex must be released atomically with the ``Wait'' so that between the Mutex 
release and the Wait another thread does not perform an operation and a
signal on the condition variable that we are about to Wait on.  We term the 
decisive step or transition as the point in the body of the implementation 
when we abstractly think of the work of the operation as happening or being 
``committed''.  There may actually be several choices as to which step
is chosen as the decisive step.

\subsection{FIFO buffer - revisited}
                          
Now, back to the FIFO description that we started before.
The SPEC and Implementation for this FIFO buffer is
on pg 9 of handout 15.  The SPEC is fairly straightforward with
three simple procedures; create, produce, and consume.  The
implementation uses mutex locks and condition variables for
synchronization.  The mutex is taken in produce and consume
around code that changes the FIFO sequence to make sure that
there are no conflicts.  Produce simply adds items to the
sequence and calls signal (to allow waiting consumers to
continue).  Consume will repeatedly loop
calling wait if the sequence is empty, otherwise, it removes
an item from the list, releases the mutex and returns.  Once
consume sees an empty sequence, it will continuously loop
until another thread gets a chance to run (due to the wait
call) and invokes the produce procedure.  It is possible that there will
be several consumers waiting and several producers or one producer
in succession will produce items before any consumer that are signalled
acquire the mutex and actually take them out.  In effect, the producers
are getting ahead of the consumers.  Each time something
is produced a signal happens and so eventually, each signalled consumer
will wake up, acquire the mutex and take an item out.

The code is relatively straightforward, but rather than use atomicity
brackets as in the spec we use mutexes.  Mutex in turn actually uses
atomicity brackets in it's implementation, but now the atomic steps
are much finer grain.  What we must do now is argue that this
implementation is correct.  There is an abstraction funtion that
states that the value of the buffer in the spec is simply the
sequence that is stored in the record in the implementation
{\tt bv.items}.
               
Let's assume that we are only dealing with one buffer and that
create is just the initialization routine of the module.
The state of the spec is the state of the buffer plus the state of 
any processes executing operations on the buffer (i.e., their program counter(s)
and their local variables).  In order to do define the abstraction
function carefully we have to say how to derive the abstract state of the
spec from the state of the implementation where the abstract state includes
all the states of the processes.  In order to do this, we need to know what
are the possible program counters (see pg 2 on handout 19).  Notice that
there are only 4 possible program counters in the specification (ignoring
create); P1, P2, C1, and C2.  In the implementation there are many more
possible pc's.  You must be careful when looking for all of the possible
atomic transactions.  In particular the statement in produce

\begin{verbatim}
b^.items := b^.items ++ t;
\end{verbatim}
                        
is two atomic steps, the first one is evaluating the right hand side
of the statement and the second one is actually assign that value to
the variable.  In addition, when attempting to count all the possible 
{\it states} the program counter can take, we use the specification for 
any calls that are made to procedures or functions outside of the current 
module.  The method that is used to produce the abstraction function for 
the pc's is; for a given thread that is inside the implementation under
consideration, if we think of that in terms of it's spec, where is it's pc?  
A good way to think about this is to make a correlation between transitions 
in the implementation versus transitions in the spec.  A transition in the 
implementation will (most often) correspond to either 0 or 1 transition in 
the spec and the external transitions will correspond one--to--one.  So, 
for example, the transition in the implementation of produce that corresponds 
to the body of produce (in the spec) is the transition that actually performs
the assignment to \verb|b^.items|.  Once the decision is made as
to which transitions correspond, the correspondence between the pc's 
is straightforward.  So, we now have two pieces of the abstraction 
function, the AF of the state (which is \verb|bv := bv.items|) and an 
abstraction function for the program counters (bottom of pg 3 handout 19).

The decisive step is the point in the implementation is where abstractly
we think of the work as being done or committed.  In the example
of produce, we could have chosen any transitions between the
point in mutex.acquire that actually acquires the mutex up to the point in
mutex.release that actually releases the mutex to be the decisive 
transition.  This is true since we guarantee that only one thread will 
have its pc in this range (due to the mutex).  We could have chosen
any transition in this range, but we just happen to have chosen the
transition that will cause the state of the implementation accurately
and most directly represents the state of the spec is modified.
If we had chosen a different decisive transition, the mapping of
pc's would change and, more importantly, the abstraction function
on the variables would have to change (in subtle ways) to accurately
reflect the actual states of the variables in both the spec and the
implementation.  If the implementation modified the (external, 
observable) state in multiple steps and the spec did it in one
single atomic operation the abstraction function could get quite 
complicated.

\section{Proofs}

To prove the implementation correct, we need some invariants because
we care about transitions in the implementation where the initial
state of the transition satisfies the invariant.  Then, we 
perform a case analysis to handle all the transitions and define 
the AF on transitions to show that these properties hold.  A brief
proof is given for the producer/consumer case in handout 19
page \# 5.  In practice, people don't go to this level of
analysis, even in a rigorous proof.  For example, people
abstract out the idea of mutexes, i.e., they (the mutexes) are
simply assumed to create atomicity brackets around
the code that they bracket with the notable exception of 
a wait call in the bracketed code.  This reduces the possible
states of the program counter and simplifies the analysis.

\end{document}

