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

\parskip = 5pt

\begin{quote}
 Computer Science is not about computers, any more than astronomy is
about telescopes. {\em Edgar Dijkstra}
\end{quote}

\section{Administrative Information}

\begin{table}[h]
\centering
\begin{tabular}{|c|l|} \hline
\multicolumn{1}{|c|}{Handout} &
\multicolumn{1}{c|}{Title} \\ \hline
17 & Problem Set \#3 \\
18 & More Examples of Concurrency \\
19 & An Introduction to Programming with Threads \\
Notes & Lectures 5, 6 \& 7 \\ \hline
\end{tabular}
\end{table}
The big news is: there will be {\em no} tests in this class.

\section{Overview}

Today's lecture will review concurrency, which was a source of a lot
of confusion during the last lecture.  We will be examining constructs
in Spec for dealing with concurrency, and end the lecture with the
topic of proving correctness of implementations in the presence of
concurrency. 

\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 constucts 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 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}

Reasoning about the semantics of Spec with concurrency is more complex
than in the sequential case because of the need to deal with arbitrary
interleavings of program executions and the state of multiple threads.

\section{Blocking and Condition Variables}

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

The simple use of spin-wait based mutexes 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 16,
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}. We will see in the
forthcoming example on Reader/Writers how each of these can be useful.

\begin{figure}
\begin{verbatim}

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

\pagebreak
The general style for using condition variables is shown in
Figure~\ref{style}.  It is important to recheck the condition after
being signalled (the {\tt \~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}
}
\section{Readers/Writers}

The Readers/Writers specification and implementation (Handout 16, p.~5
) will serve as a simple example of the use of mutexes and condition
variables.  It specifies a module that provides shared read locks and
exclusive write locks.  In {\tt ReadersWriters} the type {\tt RW} is
analogous to the type {\tt C} in {\tt Condition} with the difference
that it holds two separate sets of threads; one for readers and one for
writers.  Following is a summary of each procedure:
\begin{itemize}
\item {\tt PROC Create} creates a condition variable with an empty set
of readers and an empty set of writers.
\item {\tt PROC StartRead} first checks to see if anyone holds the
lock in write mode (others holding in read mode is OK), then adds
itself to the list of writers.
\item {\tt PROC EndRead} makes sure the calling thread is actually a
reader then removes the thread from the readers list (it does {\em
not} clear the list).
\item {\tt PROC StartWrite} first checks to see if anyone holds the
lock in {\em either} mode, and if not assigns itself to be the
``list'' of threads holding the write lock.  Note how this shows that
the set of writers will never have more than one element.
\item {\tt PROC EndWrite} empties the ``list'' of writers.
\end{itemize}

The implementation of {\tt Readers/Writers} uses the fact that only
one thread can hold the lock in write mode to keep track of the state
of a lock.  In the implementation, the entry {\tt rw} in the record
{\tt RWV} is initialized to zero and incremented for each reader
that obtains it.  When a reader releases the lock, the count is
decremented.  A writer desiring the lock can therefore only obtain it
when the {\tt rw} entry is zero, at which point {\tt rw} is set to -1.
This, in turn, acts as a signal to all other potential readers or
writers that this lock is off limits.

The implementations of {\tt StartRead} and {\tt StartWrite} show the
general style of Figure~1.  Here, a mutex is aquired and released
around the body of the procedure.  This can be thought of as simply
the implementation of the atomicity brackets.  The test being
performed in {\tt StartRead} makes sure the lock is not held by a
writer.  While it is, we pass {\tt Wait} the mutex and the appropriate
condition variable and we will not acquire the read lock until it
returns.  {\tt StartWrite} is similar, only it has the more stringent
condition of requiring that no one has the lock in any form.  In both
cases, only when the ``while'' construct succeeds will the lock
actually be acquired.

The implementation of {\tt EndRead} has an interesting hack.  After
removing itself from the list of readers, it tests to see if there are
any more readers using the lock.  If there are, it doesn't wake up any
processes, because it knows that the only ones waiting must be waiting
for a write lock, and they wouldn't be able to get it unless {\tt 
rw\^\/.rw = 0} anyway.

{\tt EndWrite}, on the other hand, clears the lock and then wakes
everyone up.  There could be readers or writers waiting on the lock
that was held, and all should be given a fair shake.  Of course, if a
process waiting for a write lock is woken up and gets to the lock
first, it will again shut out the multitude of processes waiting for a
read lock. 


\section{Topaz: An Implementation}

We then looked at an implementation of mutexes and condition variables
based on the one used in the Topaz operating system (see Handout 18).
Implementations of {\tt Spin Lock} and {\tt ReleaseSpinLock} are not
shown, but are used in the kernel to protect process queues.  Blocked
and ready process queues are maintained in the kernel which has a
scheduler to decide where to put the threads.  Entering the kernel is
expensive, so an attempt is made to do as much in user space as
possible (the fast path) and only invoke the kernel if there is a need
to block.  

\subsection{Mutexes}

In {\tt MutexImpl.Acquire} the ``fast path'' of test-and-set in user
space is taken if the lock is free.  Otherwise, the kernel is entered
and the thread is:
\begin{itemize}
\item added to the list waiting for the mutex
\item descheduled
\end{itemize}
This is the normal procedure.  An exception would be if some other
process manages to release the mutex between {\tt TestAndSet} and {\tt
KernelAcquire} in {\tt Acquire}.  This case is covered by the
statement
\begin{verbatim}
     m^.lock_bit = false => m^.queue := m^.queue.reml
\end{verbatim}
in {\tt KernelAcquire} which immediately removes itself from the queue
that it just put itself on during the previous statement and then
falls out.  This in turn simply causes the loop in {\tt Acquire} to go
around again another attempt is made to acquire the mutex in user
space.

In {\tt MutexImpl.Release} the ``fast path'' is taken if the queue of
threads waiting for the mutex is empty, in which case the mutex is
simply released.  Otherwise, the kernel is entered and a thread is
pulled from the queue and scheduled.  This thread will still have to
compete to acquire the mutex, but at least it is given a shot.

There is a bug in the definition of {\tt Release} as given in the
handout.  The corrected version, which sets the lock bit to false
before calling {\tt KernelRelease}, is: 

\begin{verbatim}

PROC Release (m) =
  m^.queue.isEmpty => m^.lock_bit := false
 [*] m^.lock_bit := false; KernelRelease(m)

\end{verbatim}

\subsection{Condition Variables}

The implementation of condition variables introduces the notion of an
event count which is an atomically updatable, monotonically increasing
integer.  A call to {\tt Signal} or {\tt Broadcast} is considered an
event and both {\tt Signal} and {\tt Broadcast} atomically increment
the event count.  If an event occurs between the time we release the
mutex in {\tt ConditionImpl.Wait} and the time we actually try to add
ourselves to the queue, then the value of {\tt eventCount} passed into
{\tt KernelBlock} will not pass the guard inside of {\tt KernelBlock}.
The procedure will just fall out and no threads will be descheduled.

{\tt Signal} increments the event count and then if the queue is
non-empty, reschedules the thread at the head of it.  {\tt Broadcast}
looks almost identical, with the exception that it schedules the
thread at the head of the queue {\em while} the queue is non-empty,
effectively rescheduling all the threads.

\section{Coming Attraction}

The next lecture will cover correctness proofs in the presence of
concurrency.  As in the sequential case, we view specifications and
implementations implementation state machines.  However, while atomic
statements correspond to state machine transitions, it is not as
straightforward in the concurrent case. 

The state machine consists of 

\begin{itemize}

\item States

\begin{itemize}
	\item variables
	\item process' states: PCs and local vars
\end{itemize}

The state of the processes are necessary in the presence of concurrency.

\item Transitions

\begin{itemize}
	\item Internal -- atomic steps and atomic transitions within
			  the body of a routine.
	\item External -- invocations, {\tt RAISE} and {\tt RETURN}.
\end{itemize}

\end{itemize}

At each point, the state constrains which of the possible transitions
are enabled.  The essential part of proving an implementation correct
is that the external behavior of the specification and implementation
should match.  The external behavior of a state machine is the
sequence of external transitions.

\end{document}

% For GnuEmacs:
% Local variables:
% compile-command: "latex lecture8"
% End:
