\documentstyle[12pt,pocs-header]{article}
\Scribes{Kathy Knobe and Dan Berkery}
\Lecturer{Bill Weihl}
\LectureNumber{7}
\LectureDate{3 October 1990}
\begin{document}
\MakeScribeTop

\section{Handouts}
\begin{itemize}
\item Handout 14: Solutions to Problem Set \#1
\item Handout 15: Spec Questions
\item Handout 16: Examples of Concurrency
\item Problem set \#1 was corrected and returned
\end{itemize}

The handout of the specification of the hierarchical file system will
not be handed out until next week or certainly by the Monday of the
following week.

\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 16: Incrementing a
Register. Given a register that has a state s which 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}

as 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 might
not be what we had in mind.

\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.) Assume 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 each processor completes the
increment before any other processor reads the current value) 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.

Question: there seem to be different semantics for this atomicity than
atomicity discussed previously. If the printing of several variables
is enclosed in atomicity brackets does this mean a crash can not occur
before the printing is complete?

Answer: Good question. In fact, liberal use of atomicity brackets
involves more magic that we can really implement.  There are two types
of atomicity that people tend to talk about. In a sense they are the
same.
\begin{itemize}
\item Atomicity with respect to failures: what are the operations that
once you start them are guaranteed to complete even in the presence of
crashes (or at least the effect will be that they totally completed or
appear as if unstarted)

\item Atomicity with respect to concurrency: what are the operations
that can not be interrupted by other processes.
\end{itemize}

But these two views are really the same if you think of crash as a
concurrent process that can wake up and crash the system between
atomic operations.  We need to know what operations will not be
interrupted.

Remember what Butler said about the disk. We need to be consistent
with the real world. We can't say that writing an arbitrarily large
amount of data is atomic. Disks don't work that way.

Consider the logging techniques that Butler talked about and our answer
to question 1 problem set 2. Its clear the we can't just sprinkle
atomicity brackets any where we want. If you are writing a
specification then atomicity brackets around large segments of code
are ok. However, if we write 
\begin{verbatim} <<disk.write; disk.write>> \end{verbatim}
then we can
reason about this as if it is atomic but we still have the task of
implementing this as atomic. If we are concerned only about
concurrency and not crashes then the implementation of large atomic
sections is straightforward. With crashes the problem is more complex.
At the start we must determine the model of failures in order to
decide what can be done atomically. Consider the appropriate level of
abstraction for your task. We can describe implementations at many
levels abstraction.

\section{Critical Sections}

Code that is supposed to run without interference from other processes
is called a critical section. This term arises because if that code is
interrupted or interleaved with code from another process, the state of
the system may become damaged. One safe way to implement a critical
section is to insure that nothing else runs concurrently with it. Such
an implementation is safe but may perform poorly. The question that
arises is: Can we maintain the appearance of atomicity and achieve
better performance? To answer this question we must understand the
source of the damage.  The code sections for P1 and P2 can be
interleaved in any way without damage. However, the code for P3 and P4
cannot.
\begin{verbatim}
P1             P2           
s1   x := 1    s4   t1 := a 
                            
s2   y := z+1  s5   t2 := t1 + b
                                
s3   w := t                     

or

     P3              P4

s1   x := 1     s4    t1 := a

s2   y := z+1   s5    t2 := t1 + y

s3   w := t

\end{verbatim}
A problem arises only when the two sections access common state
(variables, and values pointed to). If they don't access same state
then they can run concurrently. If both access the same state but both
are only reading that state we are still ok. Only in the case that at
least one
is writing and both are accessing do we have a problem. In practice
people associate a lock with shared data.  If you want to access the
data get the lock before accessing the data and release it after done
accessing the shared data. If the performance is still not good enough,
partition the shared data into subsets and associate a lock with each
subset. Then prior to entering a critical section get all the locks
that are associated with the state referenced in the critical section.
When the critical section is complete the locks are released.  All
other methods boil down to this.  NOTE: these sections do not
necessarily run atomically; they just appear to.


It is easier to reason about atomic chunks. The larger the atomic
chunks, the easier the program is to reason about. Next time we will
talk about precise reasoning techniques for concurrent programs.

\section{How Locks Work}

The approach presented on Page 2 of handout 16 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 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 lock is the one asking to
release (need some magic to get name of current thread). If the caller
to release is the current holder it releases the mutex.

Acquire is more subtle. Atomically if 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 nil and both acquire it.

\section{Semantics of Spec}

The discussion of the Acquire routine lead to a serious discussion of
the Spec semantics mostly pertaining to choice both in and out of
atomicity brackets.

 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 success 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 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}.

The system executes threads, a process can only execute the next
atomic step if that step does not fail.

Specifically, in the routine Acquire, the statement
\begin{verbatim}
<< m^ = nil => m^ := SELF>>
\end{verbatim}

the thread executing this statement sits at this statement until it
does not fail.

More examples follow:
\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 '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}
\end{document}
