\documentstyle[12pt,pocs-header]{article}
\Scribes{Rainer Gawlick}
\Lecturer{Bill Weihl}
\LectureNumber{8}
\LectureDate{7 October 1991}
\begin{document}
\MakeScribeTop

\section{Handouts}
\begin{itemize}
\item Handout 18: ``A Simple Approach to Specifying Concurrent Systems'' by
                    Leslie Lamport
\end{itemize}

\section{Today's Topic: Concurrency}
Today's topic is an introduction to concurrency. Concurrent programs
can get very hard to understand very quickly even if they are small.
Just as for sequential programs we need a way to:
\begin{itemize}
\item specify concurrent programs
\item implement current programs 
\item and prove them correct
\end{itemize}

We will talk about these tasks over the next couple of lectures.  We
will also cover various pragmatic issues that effect performance.
Start with a simple example that will illustrate the types of problems
that concurrency can cause.

\section{Incrementing a Register}

We begin with the first example in handout 15: Incrementing a
Register. Given a register that has a state {\em s} that is an integer, and
two operations:
\begin{itemize}
\item read, that returns the current state and 
\item write, that sets the current state
\end{itemize}

The operation we want to perform is an increment. In a sequential
system we could perform the increment in three steps:
\begin{itemize}
\item read the current value into a temporary
\item increment that value
\item write the updated value back into the register
\end{itemize}

This is shown on page 1 of the handout.  Now consider what happens if we
have multiple processes accessing this register via the increment
procedure. If two processes are executing this procedure concurrently,
both could execute the read operation, each could then update the
temporary, then both could write back the updated version. The
register would only be incremented once instead of twice. This is
probably not the intended behavior.

\section{Atomicity}

	To analyze this properly, we need to know the low-level atomic
operations. For some operations Spec defines the level of atomicity,
for example, the assignment is atomic once the RHS has been computed.
We can infer from this that the Read and Write procedures appear to
act atomically by examining their bodies. It is possible, however, to
implement read and write as non-atomic operations, for example, by
viewing the register as an array of bits and operating on the bits one
at a time. If operations of two processes can be interleaved at the
level of the individual bits, the result could be practically
anything.
 
	Therefore we need to know what the atomic operations are; What
are those operations that can be performed without any interleaving or
interference by any other processes. Without knowing this we can't
understand a concurrent program.  The way to think about a concurrent
program is that the processes each take steps and that they are
running asynchronously.  The execution of the program is then as
follows: at each time step we pick a process and that process does the
next atomic step according to its own PC.  We then pick the next
process to run the next step more or less randomly.  The result is
interleaving at the level of the atomic operations.

	A major issue in concurrent programming is how to make complex
operations (like increment) appear atomic. In Spec we have atomicity
brackets to define larger atomic operations from smaller ones. (Spec
uses double brackets. Often in the literature we will see single
brackets.) Code inside atomicity brackets executes atomically.

In the second example in handout 16, assume the read and write are
atomic. If the register started with value k and n processors are
concurrently executing the increment, the possible result values
include any integer from k+1 (if the all read the current value before
any of them update it) to k+n (if the processors perform the
increments in sequence) and
anything in between. Recall if the reads and writes are not atomic
then the possible results might be quite different.


To bring the point home, suppose the increment is adding money to a
bank account. If you and your spouse both increment your account you'd
like both operations to have an impact on your balance.

This problem appears at all levels of concurrent systems, register
operations (as shown here) and at higher levels such as operating
systems and data bases etc.

The simplest way to make complex operations atomic is to enclose them
in atomicity brackets.  This feature is not something that most
languages provide (although there are some proposed languages that
do). One way to implement the atomicity bracket is to not allow any
other process to execute their next step while one process is
executing code within atomicity brackets.  On multiprocessors, this
wastes cycles on the idle processors and can have serious impact on
performance. Even on uniprocessors we wouldn't want to implement
atomicity brackets this way. Suppose that the process performs I/O,
the process would have to wait until the I/O is done before execution
proceeds.

\section{Locks}
Code that is supposed to run without interference from other processes
is called a {\it critical section}. This term arises because
the state of a system may become damaged  if critical section code is
interrupted or interleaved with code from another process.
We saw an example of this in the 
previous section with the increment procedure.
One safe way to implement a critical
section is to insure that no two processes are executing the critical
section code concurrently.
Exclusive use of the critical section is usually achieved with
locks. The approach to locks
presented on Page 2 of handout 15 is used in many systems
today. Mutex is a standard term in the literature; it stands for
mutual exclusion. This spec provides a way to create a new mutex (will
be more than one mutex). The state of the mutex is {\tt nil} if no process
is holding a lock or it is the thread name (process id) of the process
that is holding the lock.

[Aside: There is a confusion between the term process (this has
connotation of separate address space) and the term thread.  For this
class the differences are not critical.]

This spec also provides for releasing and acquiring locks. Release
atomically sees if the process holding the lock is the one asking to
release. If the caller to release is the current holder, it releases
the mutex.

Acquire is more subtle. Atomically if the current state of the mutex is
nil it is set to the name of the current process. This must be done
atomically---if it is not done atomically race conditions could occur
in which two processes both find it {\tt nil} and both acquire it.

An implementation of Mutex is given on page 3 of handout 15 under
the heading ``Spin Locks''.  Consider the module BrokenMutexImp.  The
implementation of {\tt PROC Release} and 
{\tt PROC Create} are rather straight forward.  
Note that, unlike the Mutex Spec, this implementation does not store
the id of the process that is currently holding the lock.  The 
implementation of {\tt PROC Acquire} is slightly incorrect.  
{\tt PROC Acquire} has two 
atomic statements.  One checks to see if $m = $ held, the other sets
$m :=$  held.  If two processes both execute the first statement and find
$m \neq$ held  and then both execute the second statement and set
$m :=$ held, both processes will believe that they have acquired the lock.
This situation clearly violates the Mutex spec.  The race condition 
illustrated by the example we just described is eliminated in 
the module MutexImp, which is given on page 4 of the handout.

Note that a process wishing to acquire the lock will continue to read
the state of the mutex until the lock is successfully
acquired.  Locks with this type of behavior are called {\it
spin locks}.  The performance issues related to spin locks
will be discussed later in the lecture.

\section{Semantics of Spec}

 Recall that atomic statements were modeled as relations on states.
Nonatomic statements are more complex.  We model them as a set of
traces where each trace consists of a sequence of state transitions.
Which of the traces a nonatomic statement will follow depends on the
state of the system at the time that each step in the statement
is executed.  Since steps from other processes can alter
the state of the system and can be interleaved
between the steps of the statement, it cannot be known ahead of time which
steps a nonatomic statement will execute.  In contrast, atomic
statements do not allow any interleaving of steps from other processes.
Hence, the trace that an atomic statement would follow is well
defined when the statement begins its execution. This fact
allows us to model atomic statements as relations on states.

We will now explore the differences between the semantics of atomic 
and nonatomic statements with some examples.

 If we have an atomic statement that can fail (the body of the Acquire
can fail if value of mutex is non nil) then the semantics is as if it
were implemented with backtracking in order to find a successful path
through the statement that succeeds. In other words, inside atomicity
brackets, we use all the angels and backtracking mechanisms necessary
in order to make the right non-deterministic choices so that the
statement will succeed.

Outside of atomicity brackets, we can choose the next single atomic
step based on success or failure of that single step only.  That is,
for
\begin{verbatim}
<s1>;<s2>;<s3> []  <s4>;<s5>;<s6> or 
<s1>;<s2>;<s3> [*]  <s4>;<s5>;<s6> 
\end{verbatim}

We can choose an option based on the success of the first atomic operation
of that option {\tt  (s1 or s4 )} only. If we choose the option
{\tt (<s1>;<s2>;<s3>)} over the option {\tt( <s4>;<s5>;<s6>)} then {\tt <s1>}
 must have
succeeded. However, if {\tt <s2>} fails no backtracking takes place. The
program counter remains at {\tt <s2>} until (based on updates by other
processes) {\tt <s2>} succeeds.

Two aspects  make these semantics rational:

	We can't backtrack over a completed atomic operation (e.g.,
{\tt <s1>}) since other interleaved processes may have performed subsequent
steps based on the completion of that atomic operation.
	We can't use an oracle or an angel to ensure that all the
statements will succeed for an option composed of several atomic
operations since after one of the atomic operations completes, other
processes could perform actions that affect the guards of the
remaining operations. In this example, we can't guarantee the success
of {\tt <s2>} and {\tt <s3>} since after completion of {\tt <s1>} 
another process could
update values used in computing the guard for {\tt s2}.

Consider the following additional examples:
\begin{itemize}
\item 
\begin{verbatim}
<<
G1 => S1 
[]
G2 => S2
>>
if G1 => S1 fails then try G2 => S2.
\end{verbatim}
\item
\begin{verbatim}
<<S1>>; <<P => S3>>
[]
<<S2>>
\end{verbatim} 
Choice is made on the success or failure of {\tt S1} and {\tt S2}. If {\tt S1} 
succeeds and we choose the first of the two options {\tt P } can still fail. 
Since
this choice is outside atomicity brackets we cannot backtrack.  We
simply wait at {\tt <<P => S3>>} until it succeeds.

\item

If the previous example were changed slightly to 
\begin{verbatim}
<<S1>>; P => S3
[]
<<S2>>
\end{verbatim} 
then if we choose the first option, we execute {\tt S1} atomically and then
try {\tt P}. If {\tt P} fails we wait, as before, until {\tt P}
succeeds. However, if {\tt P} succeeds, with the 
atomicity brackets removed, it is possible that other processes
intervene between the success of P and the execution of {\tt S3}.

\item
\begin{verbatim}
<<
S; P => S3
[]
S2
>>
\end{verbatim} 
Here we cannot pick the first choice if {\tt P} fails.

%Kathy, this seems to be a repeat of the third item.
%\item

%By modifying the atomicity brackets in example above we have:
%\begin{verbatim}
%(<<S>>; P => S3)
%[]
%<<S2>>
%\end{verbatim} 
%Here we can pick the first choice even if {\tt P} fails. We will simply wait
%at {\tt P => S3} until {\tt P} succeeds.

\item
\begin{verbatim}
SKIP; false => HAVOC
[]
SKIP
\end{verbatim} 
Here we could choose the first option since SKIP succeeds but we will
wait forever at the predicate {\tt false}.

% Kathy, I could not find a good place to put this in.
%[GOES SOMEWHERE] One way to think about this is that atomic statements
%are simply relations from input states to output states. Semantics of
%a series of non-atomic statements are more complicated.

\item
\begin{verbatim}
<<S1>>; <<P>> => <<S3>>
[*]
<<S2>>
\end{verbatim} 
Choice examines only {\tt S1} and {\tt S2} for success. If both fail, it is the
choice that is waiting.  That is, when either succeed, processing can
continue.

\end{itemize}

\section{SPEC's handling of Concurrency}

There are only a few fundamental additions to SPEC which allow it to
handle concurrency.  These are summarized below:
\begin{enumerate}
\item {\bf Semantics} of non-atomic statements are different than those
for atomic statements.  Non-atomic statements often represent a
sequence of transitions rather than a single transition and therefore
have the potential to get stuck in the middle of a statement.
\item {\bf Atomicity brackets} are used for specifying long constructs that are
intended to be done atomically.
\item {\bf Primitive atomic operations} must be defined to allow us to
reason about atomicity.  The following are primitive atomic operations:
  \begin{itemize}
  \item Expression evaluation
  \item Functions
  \item Guards
  \item Choosing x {\em and} first transition (underlined) in the
following statement:\\ {\tt \underline{VAR x | P(x)} => S}
  \end{itemize}
Assignment is {\em not} an atomic operation: it occurs in two steps.
The right hand side is first executed then the left hand side is
executed.  The actual assignment after the rhs and lhs have been
executed is atomic.
\item {\bf Fork} allows you to fork another thread.
\end{enumerate}

\section{Blocking and Condition Variables}

Refer to Handout 15 for the examples on mutexes and condition
variables.

The simple use of spin locks is silly on a uniprocessor
because the process doing the spinning is probably waiting for a
different process to release the mutex being waited on.  Even on
multiprocessors, it is often desirable to {\em block} rather than
spin.  Blocking is used to stop an otherwise spinning process from
executing until there is a chance that the mutex it is waiting for can
be acquired.  This prevents the spinning process from stealing
processor time while it waits.

Conditional variables are a more sophisticated mechanism for
synchronization than mutexes.  They allow threads that are waiting for
a certain condition to be blocked until signalled.  The signalling can
be to a minimum number of blocked threads, typically one, ({\tt
Signal}) or to all waiting threads ({\tt Broadcast}), thereby allowing
the user some measure of control over thread scheduling.  Several race
conditions can occur in the process of scheduling and descheduling
threads and the specification has to be careful to avoid these race
conditions.

Let us examine the specification of condition variables in Handout 15,
p.~4.  In this module, {\tt CV} defines a type which will hold the
list of waiting threads.  {\tt M} is a mutex which the wait procedure
uses to make sure that the addition of a thread to the waiting list
happens atomically, i.e., no other thread causes the condition to be
true (and signal any waiters) while this thread is putting itself on
the waiting list.  (This also requires that any thread that wants to
make the condition become true has to acquire the mutex first.)  If
this convention was not followed, the thread might never be woken up.
This is known as the wakeup-waiting race.

We can now examine {\tt PROC Wait} (see Figure 1) in detail.  

\begin{figure}
\begin{verbatim}

   PROC Wait (m,c) = 
     m^ # SELF => HAVOC                                    1
    [*]                                                    2
     << c^ := c^ ++ SELF; m^ := nil >>;                    3
     << (m^ = nil) /\ ~(SELF IN c^) => m^ := SELF >>       4

\end{verbatim}
  \caption{Specification of {\tt Condition.Wait}}
\end{figure}

It is assumed that the caller of {\tt Wait} has already acquired the
mutex.  The first guard shows the consequence of calling {\tt Wait}
without first acquiring the mutex: havoc results.  Otherwise, the
procedure performs the following sequential atomic operations:

\begin{enumerate}
\item Add the calling thread to the waiting list and release the
mutex (line 3).
\item Waits on the next atomic statement until the guard is
true; namely until the mutex is free and this thread has been removed
from the waiting list (line 4).  At this point, the mutex is acquired
and the calling procedure can continue, safe in the notion that the
condition is now satisfied.
\end{enumerate}

The two atomic steps in {\tt Wait} look rather strange because we
release the mutex in one step and then test to see if it is released
in the next.  The important thing to note here is that we are {\em
depending} on the intervention of another process during the semicolon
between the two atomic statements.  This intervention is simply the
removal of this thread from the waiting list.  This removal is
accomplished by one of the two remaining procedures in this module.
The first, {\tt Signal}, picks some subset of waiters to wake up.  The
second, {\tt Broadcast}, has the effect of removing all the waiting
threads.  The reason {\tt Signal} picks some subset of waiters to
resume rather than just a single waiter is to allow for a more
efficient implementation of {\tt Signal}. 

\begin{figure}
\begin{verbatim}

                 Mutex.Acquire(m)
                 DO ~test => Wait(m, c) OD
                       :
                       :  (useful stuff)
                       :
                 Mutex.Release(m)
\end{verbatim}
  \caption{Style in use of Condition Variables}
\label{style}
\end{figure}

The general style for using condition variables is shown in
Figure~\ref{style}.  It is important to recheck the condition after
being signalled (the \verb|~test| in the do loop) to avoid a
wakeup-waiting race.  If the condition is not rechecked, the following
sequence of events can occur.

{\advance\baselineskip by -3pt
\begin{verbatim}
     Thread 1                  Thread 2

      Block
                                Acquire
                                Signal
                                Release
                        |
                        | Time window in which someone else invalidates
                        | condition satisfied by Thread 2.
                        |
      Acquire
	
    Thread 1 has mutex here
   and thinks condition is 
   satisfied when it really isn't.

\end{verbatim}
}

\end{document}
