Introduction To Distributed Systems
Introduction To Distributed Systems
Introduction To Distributed Systems
Spring 2007
ii
c 2007
Paolo A.G. Sivilotti
All rights reserved
Contents
iii
iv CONTENTS
2.6 Fairness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.6.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.6.2 Weak Fairness . . . . . . . . . . . . . . . . . . . . . . . . 24
2.6.3 Strong Fairness . . . . . . . . . . . . . . . . . . . . . . . . 24
7 Mutual Exclusion 77
7.1 Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.2 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.3 Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
7.3.1 A Trivial Solution . . . . . . . . . . . . . . . . . . . . . . 79
7.4 Distributed Atomic Variables . . . . . . . . . . . . . . . . . . . . 79
7.4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 79
7.4.2 Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
7.5 Nontoken-Based Solutions . . . . . . . . . . . . . . . . . . . . . . 82
7.5.1 Lamport’s Algorithm . . . . . . . . . . . . . . . . . . . . . 82
7.5.2 Optimization #1 . . . . . . . . . . . . . . . . . . . . . . . 83
7.5.3 Optimization #2: Ricart-Agrawala . . . . . . . . . . . . . 83
7.6 Token-Based Solutions . . . . . . . . . . . . . . . . . . . . . . . . 84
7.6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.6.2 Simple Token Ring . . . . . . . . . . . . . . . . . . . . . . 84
7.6.3 Token Ring with Requests . . . . . . . . . . . . . . . . . . 85
7.6.4 Token Tree (Raymond) . . . . . . . . . . . . . . . . . . . 87
7.6.5 Token Graph . . . . . . . . . . . . . . . . . . . . . . . . . 88
7.6.6 Summary of Key Ideas for Token-based Solutions . . . . . 88
8 Dining Philosophers 91
8.1 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.2 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.3 Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
8.4 Naive Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
8.5 Hygienic Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
8.6 Refinement of Specification . . . . . . . . . . . . . . . . . . . . . 94
8.6.1 Safety (Forks) . . . . . . . . . . . . . . . . . . . . . . . . . 94
8.6.2 Priority (Clean vs. Dirty) . . . . . . . . . . . . . . . . . . 94
8.6.3 Neighbor Hunger (Request Tokens) . . . . . . . . . . . . . 96
8.7 Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
8.7.1 Message-Passing Pseudocode . . . . . . . . . . . . . . . . 97
8.8 Proof of Correctness . . . . . . . . . . . . . . . . . . . . . . . . . 99
vi CONTENTS
9 Snapshots 101
9.1 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
9.2 Problem Description . . . . . . . . . . . . . . . . . . . . . . . . . 101
9.3 The Naive Approach . . . . . . . . . . . . . . . . . . . . . . . . . 102
9.4 Consistent Cuts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.5 Solution #1: Logical Time . . . . . . . . . . . . . . . . . . . . . . 104
9.6 Utility of Snapshots . . . . . . . . . . . . . . . . . . . . . . . . . 105
9.7 Solution #2: Marker Algorithm . . . . . . . . . . . . . . . . . . . 106
9.7.1 Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
9.7.2 Proof of Correctness . . . . . . . . . . . . . . . . . . . . . 107
ix
x LIST OF FIGURES
xi
xii LIST OF TABLES
Chapter 1
1.1 References
The following references are both very good:
1.2 Preliminaries
1.2.1 Functions
A function is a mapping, or relationship between elements from two sets. A
function maps values in one set (the domain) to values in the other (the range).
By definition, a function maps each element in its domain to at most one element
in its range. We use the notation f : A → B to indicate a function f whose
domain is the set A and whose range is the set B . For example, the familiar
1
2 CHAPTER 1. BOOLEANS, PREDICATES, AND QUANTIFICATION
This describes a function named sqrt that maps elements from the positive real
numbers to the positive real numbers.
The most basic operator for a function is function application. This operation
is denoted by a dot (i.e., “.”). For example, we would write:
sqrt.16
Contrast this with the more common notation for function application, which
appears something like sqrt(16) . Although the later notation has the advantage
of similarity with several common imperative programming languages (e.g., C,
Pascal, and Java), we choose the former notation because it lends itself nicely
to Currying.
This does mean, however, that we must exercise care when dealing with
functions with more than one argument. For example, consider the function
max2 that takes the maximum of two integers. The signature of this function
is:
max2 : ZZ × ZZ → ZZ
Application of such a function to its arguments will be written as max2.(a, b)
(and not max2.a, b which would force us to distinguish two very similar characters—
“.” and “,”—to resolve the meaning of such expressions).
The dot operator associates to the left, so f.x.y should be interpretted as
(f.x).y . For such an expression to make sense, the result of applying f to x
must be a function! This function is then applied to y . Any functions with
multiple arguments can be redefined in this manner. For example, consider the
max2 function above. We can define a new function, max , where we would
write max.a.b . What is the type of max ? Make sure you can graph max.4
(which is a function in one argument).
• etc...
true ∧ false
S
domain of
the predicate
Mikko
In computer science, the domain of a predicate is quite often the state space
of a program. For example, in the context of a program with two variables, x
and y , both integers, we might write expressions such as even.x , prime.y ,
and x + y = 3 . These are all boolean expressions that are either true or false
for each point in the state space.
1.2.3 Lifting
The distinction between boolean (a set with two elements: true and false)
and predicate (a function with a particular range, i.e., boolean) seems basic
4 CHAPTER 1. BOOLEANS, PREDICATES, AND QUANTIFICATION
Mikko
Jeff
Sindhu
is_heavy is_tall
In this way, you can complete Figure 1.3 by shading the areas in which the
element is mapped to true.
is_heavy is_tall
But what if we wanted to talk about the equivalence of the two predicates
themselves? In other words, what if we want to state the claim “Being tall is
the same as being heavy”? This claim is either true or false. It is not true
sometimes and false other times. So what is going on here?
The problem is that the claim really involves an implicit quantification.
Claiming that “Being tall is the same as being heavy” can more precisely be
restated as “For every person, being tall is the same as being heavy”. Thus,
we could use explicit quantification to state our claim. This issue turns out
to be so pervasive, however, that it is worth introducing a short-hand for this
quantification. We write:
The domain over which the quantification occurs is understood from context.
When it matters, it is almost always the state space of some program under
consideration. The square brackets are known as “everywhere brackets”.
Notice that the expression is now a . Indeed we can enclose
any predicate with everywhere brackets, resulting in a .
[is heavy] is
[is tall ∨ is heavy] is
[is tall ∨ ¬is tall] is
6 CHAPTER 1. BOOLEANS, PREDICATES, AND QUANTIFICATION
(sqrt.16 = 4) ∧ (4 = 22 )
Equivals can also appear multiple times on the same line, but—in general—
we do not intend this to represent chaining. An expression with multiple occur-
rences of equivals is evaluated (or simplified) directly. For example:
false
| ≡ true} ≡ false
{z
≡ false
| {z }
∧
| {z }
[((X ≡ Y ) ≡ Z) ≡ (X ≡ (Y ≡ Z))]
Axiom 2. Commutativity of ≡ .
[X ≡ Y ≡ Y ≡ X]
Here we make use of the associativity of ≡ to write the axiom without
parentheses. The resulting axiom is quite rich. It captures the property of
commutativity, with:
[(X ≡ Y ) ≡ (Y ≡ X)]
It also, however, can be parenthesized as follows:
[X ≡ (Y ≡ Y ) ≡ X]
This exposes an interesting fact: the expression (Y ≡ Y ) is both the left and
right identity of ≡ . An identity element is worth naming, and we do so next.
Axiom 3. Definition of true.
[Y ≡ Y ≡ true]
Again, notice the effect of different placements of parentheses. By interpret-
ing this axiom as [Y ≡ (Y ≡ true)] we see that true is indeed the (right)
identity of ≡ . In other words, there is no difference between writing [Y ] and
[Y ≡ true] .
1.3.2 Disjunction
While ≡ is interesting, just one operator is a little limiting. So, we introduce
a new operator, called disjunction and written ∨ (pronounced “or”). To reduce
the number of parentheses, we define an order of operations: ∨ is defined to
bind more tightly than ≡ .
We define this new operator by the following four axioms.
Axiom 4. Associativity of ∨ .
[X ∨ (Y ∨ Z) ≡ (X ∨ Y ) ∨ Z]
Axiom 5. Commutativity of ∨ .
[X ∨ Y ≡ Y ∨ X]
Axiom 6. Idempotence of ∨ .
[X ∨ X ≡ X]
The last axiom describes how our two operators interact with each other.
Axiom 7. Distribution of ∨ over ≡ .
[X ∨ (Y ≡ Z) ≡ (X ∨ Y ) ≡ (X ∨ Z)]
The definition of these two operators (i.e., the axioms given above) form the
foundation of our presentation of the predicate calculus. There are many other
choices for where to begin this presentation. A different set of axioms could
have been chosen and then the associativity of ∨ , for example, derived as a
theorem.
8 CHAPTER 1. BOOLEANS, PREDICATES, AND QUANTIFICATION
A
≡ { reason why [A ≡ C] }
C
≡ { reason why [C ≡ B] }
B
2
A ≡ B
≡ { reason why [A ≡ B ≡ D] }
D
≡ { reason why [D ≡ true] }
true
2
Writing proofs in this way will give us a common structure in which to read
each other’s proofs. It will also encourage a nice discipline of justifying steps in a
precise manner. Notice, also, that expressions written in this format have a very
different meaning than their straight-line counterparts. That is, the meaning of
A
≡
C
≡
B
1.3. THE PREDICATE CALCULUS 9
[X ∨ true ≡ true]
Proof. X ∨ true
≡ { definition of true }
X ∨ (Y ≡ Y )
≡ { distribution of ∨ over ≡ }
(X ∨ Y ) ≡ (X ∨ Y )
≡ { definition of true, with Y as X ∨ Y }
true
[X ∨ Y ≡ X ≡ Y ≡ (X ∧ Y )]
Axiom 9. Definition of ⇒ .
[X ∨ Y ≡ Y ≡ (X ⇒ Y )]
[X ∨ Y ≡ X ≡ Y ≡ X ∧ Y ]
[X ∨ Y ≡ Y ≡ X ⇒ Y ]
10 CHAPTER 1. BOOLEANS, PREDICATES, AND QUANTIFICATION
[X ∧ true ≡ X]
Proof. X ∧ true
≡ { }
≡ { }
≡ { }
X
Theorem 3. Associativity of ∧ .
[X ∧ (Y ∧ Z) ≡ (X ∧ Y ) ∧ Z]
Proof. X ∧ (Y ∧ Z)
≡ { Golden Rule }
≡ { ∨ over ≡ twice }
X ∨ Z ≡ Y ∨ Z ≡ X ∨ Y ∨ Z ≡ X ≡ Y ≡ X ∨ Y ≡ Z
≡ { ∨ over ≡ twice }
≡ { Golden Rule }
(X ∧ Y ) ∧ Z
[¬X ∨ X]
1.3. THE PREDICATE CALCULUS 11
[¬(X ≡ Y ) ≡ X ≡ ¬Y ]
Again, many interesting properties can be derived for this new operator.
Theorem 4. ¬ is an involution.
[¬¬X ≡ X]
Proof. ¬¬X ≡ X
≡ { }
≡ { }
≡ { }
≡ { }
true
Theorem 5. [X ⇒ Y ≡ ¬X ∨ Y ]
Proof. (For proof, see homework.)
We introduced a special symbol for the identity of ≡ (i.e., true). It turns
out to be useful to have a special symbol for the negation of this identity. It is
written false, and defined by a single axiom.
Axiom 14. Definition of false.
[¬true ≡ false]
1.3.6 Discrepance
The symbols introduced so far have been very familiar. It can also be a use-
ful exercise for honing your skills with this calculational approach to consider
less common operators. In particular, we now examine the discrepance opera-
tor, written 6≡ (and pronounce “differs from”). We pursue this investigation
primarily as an exercise in using and working within the calculation style.
Discrepance is defined by a single axiom.
Axiom 15. Definition of 6≡ .
[¬(X ≡ Y ) ≡ (X 6≡ Y )]
Theorem 6. Commutativity of 6≡ .
[(X 6≡ Y ) ≡ (Y 6≡ X)]
Theorem 7. Associativity of 6≡ .
This operator “feels” (and looks!) a lot like ≡ , so a natural question would
be how do these two interact? In particular, how does equivalence distribute
over discrepance (i.e., [X ≡ (Y 6≡ Z) ≡ ?] )? It turns out, the following
(perhaps somewhat surprising) property can be proven.
Theorem 8. Mutual associativity of ≡ and 6≡ .
This means that in a chain of ≡ ’s and 6≡ ’s, we can leave off the paren-
theses! For example, we might write:
[A ≡ B ≡ C 6≡ D ≡ E 6≡ F 6≡ G]
The intuition behind the “meaning” of such an expression can be tricky, but the
calculational style lets us manipulate it, massage it, and (possibly) simplify it.
One useful property for working with discrepance is:
Theorem 9. Parity of 6≡ .
[(X 6≡ Y 6≡ Z) ≡ ]
Another name that is frequently used for discrepence is “exclusive or”, some-
times writen ⊕ . This is an example of bad notation! This bad notation makes
some basic transformations appear strange. For example, consider the distri-
bution of ⊕ over ≡ . Confronted with the expression X ⊕ (Y ≡ Z) it is
tempting to write . It takes considerable force of will
to write the correct result of the distribution, (X ⊕ Y ) ≡ Z . Good notation
should suggest the rule.
Of course, even with good notation our intuition can lead us astray. For
example, consider the distribution of ∨ over 6≡ . A reasonable, intuitive,
guess for such a rule might be:
[X ∨ (Y 6≡ Z) ≡ ]
But this is not correct. When we go to verify our intution through calculation,
we discover:
X ∨ (Y 6≡ Z)
≡ { }
≡ { }
1.4. QUANTIFICATION 13
≡ { }
≡ { }
≡ { }
≡ { }
1.4 Quantification
1.4.1 Syntax
You are certainly familiar with summation and product notation. For example,
you can immediately calculate:
5
X
i =
i=2
4
Y
i =
i=1
But you may not have thought very carefully about the definition of these sym-
bols. The trouble with intuitive understandings isQ that they can be ambiguous.
1
For example, what would you say is the value of i=4 i ?
The summation and product expressions above are actually both examples
of quantification! You’ve certainly encountered quantification in the form of “for
all” and “there exists”, but in this section we will present a general definition (and
syntax) of quantifications that subsumes these particular forms. The general
notation will allow us to introduce and work with new forms of quantification
that will prove to be convenient short-hands.
A quantification has the form:
( Q i : r.i : t.i )
where Q is the “operator”, i is the “bound variable”, r.i is the “range”, and t.i
is the “term”. In order to be a valid quantification expression, these elements of
the expression must satisfy certain constraints.
• The term must be an expression (that may contain i ) and the type of this
expression must be the same as the type of the operands of the operator.
Thus, for a bound variable of type T , the range is a predicate on T , the
term is an expression of some type D , and the quantifier an operation of type
D×D →D.
(∗i : 1 ≤ i ≤ 4 : i )
=
=
2.
( ∧ n : n ≥ 0 : even.n )
=
=
Operator Quantification
Symbol
∧ ∀
∨ ∃
P
+
Q
∗
Our definition of quantification is more general than these four symbols, how-
ever. Any appropriate operator (i.e., satisfying the requirements) can be used.
1.4. QUANTIFICATION 15
For example, you should now understand the following quantified expressions
and be able to write out their expansions:
1. ( ∪ i : i ∈ ZZ ∧ even.i : {i} )
2. ( Min n : n ∈ IN ∧ even.n : (n − 3)2 )
As a short-hand, the “type” of the bound variable is often understood from
context and/or from convention. For example, i is often used as a bound vari-
able ranging over the integers, while n is used as a bound variable ranging over
the naturals. Thus, the second quantification above could be more succinctly
written:
( Min n : even.n : (n − 3)2 )
As a further short-hand, when the range is the predicate true, it can be
omitted all together. For example, we write:
( ∀ i :: i2 > 0 )
1.4.3 Definition
Q1
This section began by posing the question concerning the meaning of i=4 i .
Armed now with our informal understanding of the expansion of quantification,
we are prepared to write out the solution.
1
Y
i
i=4
= (∗i : 4 ≤ i ≤ 1 : i )
=
( Q i : false : t.i ) = u
( ∀ x : x2 < x : x = 23 ) ≡
In other words, we need to find a u such that u Min x = x , for any x . This
suggest the correct value for u is . Therefore, the previous expression
is properly simplified as:
( Q i : i = E : t.i ) = t.E
2.1 References
Our programming notation and model of computation follows very closely the
presentation in “Parallel Program Design: A Foundation” by K.M. Chandy and
J. Misra (Addison-Wesley, 1988) [CM88]. This is the canonical reference for
UNITY logic.
2.2 Programs
Programs consist of two things:
The first thing to notice is that the collection of assignments is a set. Assign-
ments are therefore not ordered!
The variables are declared at the beginning of the text of the program, in
a section beginning with the keyword var. The assignments are listed in the
next section (called the “assign” section), indicated by the keyword assign. A
“fatbar” ( [] ) is used to separate assignments.
As a simple example of this notation, consider the following program:
Program T rivial
var x, y : int
17
18 CHAPTER 2. THE COMPUTATIONAL MODEL
assign
x := 2
[] y := f.7
The type of the variables will be omitted when understood from context.
By convention, all programs implicitly contain the simplest assignment of
all: skip. This assignment leaves the state unchanged (i.e., it does not modify
any variables). Even if skip is not explicitly listed in the assign section, it is
still part of the program’s set of assignments. As a consequence, no program
has an empty set of assignments.
Optionally, programs may also contain some initial conditions. These are
listed in the “initially section”, denoted by the keyword initially. This section (if
present) is included after the variable declarations and before the assign section.
Note that the initially section is a predicate (on the program variables).
2.3 Actions
2.3.1 Simple Assignments
Each action must terminate. Since an action is just an assignment, this is not
difficult to guarantee. An assignment can, however, be nondeterministic. For
example, the following action assigns a random integer in the interval [1..10] to
x:
x := rand(1, 10)
Actions must, however, be total. That is, they must be defined for every
possible program state. So, in a program with two integer variables, x and y ,
the following assignment is not permitted:
x := x/y
x, y := 2, f.3
The meaning of such a multiple assignment is that, in one action, all expressions
on the right-hand side are evaluated and then the assignments to the variables
listed on the left-hand side are made. So, the values of two variables are swapped
by the action:
x, y := y, x
2.4. OPERATIONAL INTUITION 19
The second notation is a little less compact, but lends itself to quantification.
A parallel operator ( k ) is used to separate the multiple assignments. The
swapping example above would be written:
x := y k y := x
x > 0 −→ x, y := 2, f.3
The guard is a predicate on the state space of the program. If the guard is true
in some state, the action is said to be “enabled” in that state.
You can think of each action being written on a separate piece of paper. All
the pieces of paper are placed in a hat. Program execution consists of reaching
into the hat, selecting an piece of paper, and executing the specified action.
The piece of paper is then placed back into the hat, and the selection process
repeated.
There are very few restrictions on which action is chosen. An action could
be chosen several times in a row, for example. There are, however, some restric-
tions. These are termed “fairness” properties and will be discussed at the end
of this chapter.
What if the selected action is guarded and the guard is false? In this case,
the action is simply a skip (i.e., it does not change the program state).
Another natural question to ask is when does a program terminate? When
do we consider the computation to be “over”? We address this issue next.
2.4.2 Termination
The nondeterministic selection of actions continues without end. There is, there-
fore, no “last” action picked and executed. Consider, however, the following
program:
Program Boring
initially x = 2 ∧ y = f.7
assign
x := 2
[] y := f.7
Clearly this program is not very exciting. Whichever action is selected, the
state of the program does not change. The selection of actions continues forever,
but the state of the program remains fixed. The computation can certainly be
viewed as “done”.
So what we care about, then, for termination is the arrival of the program
execution to a fixed point (abbreviated F P ). A fixed point is a state in which
the execution of any action leaves the state unchanged.
By looking at the assign sections of programs, we can calculate their fixed
points. For example, consider a program with an assign section:
assign
x := y
[] y := f.7
assign
x = y −→ x := 2
[] y := f.7
In this case, F P = .
In general, to calculate F P , require each action to have no effect. So, for a
guarded action of the form g −→ x := E , we require: .
Program F indM ax
var A : array 0..N − 1 of int,
result : int
initially result = A[0]
assign
( [] x : 0 ≤ x ≤ N − 1 : result := max(result, A[x]) )
The first thing to notice about this example program is the assignment sec-
tion. The one line in this section is actually a quantification! The operator is
[] , the free variable is x , the range is 0 ≤ x ≤ N − 1 , and finally the term is
result := max(result, A[x]) . As an exercise, can you write out the expansion
of this quantification? You should see the result is a set of assignment actions.
What do you suppose is the identity element for the [] operator?
Next, consider the fixed point of this example. In order for the first action
in the set (i.e., in the quantification) to leave the state unchanged, it must be
the case that:
result = max(result, A[0])
result ≥ A[0]
Therefore, in order for no action to change the state of the program, it must be
the case that:
( ∀ x : 0 ≤ x ≤ N − 1 : result ≥ A[x] )
Actions
States
Recall that there were several requirements placed on programs in the pre-
vious section. These requirements each have implications on the nature of the
directed graphs that we end up drawing to represent programs. In particular:
• All programs include skip. This means that all directed graphs include
. These edges corresponding to the implicit skip
action are usually omitted from the graph (and understood to be implicitly
present).
• All actions are total (i.e., defined for every state). This means that each
vertex has .
You can think of each edge as being labeled (or colored) by its corresponding
action. Complete Figure 2.2 with an example of a possible program, with five
states in its state space and three actions (not including the implicit skip).
2.5.2 An Exercise
Consider the following program:
Program Example1
var b : boolean,
2.5. VISUALIZING A PROGRAM 23
n : {0, 1, 2, 3}
initially n=1
assign
b −→ n := n +4 1
[] n = 0 −→ b := false
The addition in the first assignment is performed modulo 4. Draw the corre-
sponding directed graph in the space provided by Figure 2.3. Draw the vertices
and the directed edges. Identify which edges correspond to which action (by la-
bels or by color). Also, circle the set of states that satisfy the initially predicate.
2.6 Fairness
2.6.1 Motivation
Because every program includes implicitly a skip action, every program (apart
from the trivial one, with an empty assign section) consists of multiple actions.
How do we choose which action is executed next? Recall our informal model of
computation had us reaching into a hat each time and picking an action. What
if we are extremely unlucky and happen to pick the skip action each and every
time? None of the other actions are ever chosen and the program does nothing.
To prevent this kind of “unlucky” selection, we impose a fairness requirement
on the selection of actions. There are two kinds of fairness: weak and strong.
even say malicious) selection in which one action (or more) is perpetually ig-
nored. In the previous example, the selection was also extraordinarily unlucky
(one might even say malicious). One action was only chosen at particular times,
when it happened to be unenabled.
Strong fairness requires that each action be selected infinitely often (like
weak fairness), and furthermore, that if an action is enabled infinitely often, it is
selected infinitely often. Under a model of computation with strong fairness, the
program Example 1 would not be allowed to cycle through the state < true, 0 >
infinitely often without selecting the action that assigns false to b . Under this
model, therefore, the program would be guaranteed to reach a fixed point.
To further contrast “strong” and “weak” fairness, you should notice that if
we reason about a program using weak fairness, and prove some property, P ,
then P is indeed a property of the program regardless of whether the actual
execution is strongly or weakly fair. Conversely, however, if we prove a property
of a program assuming strong fairness, then this property may not hold under
a weakly fair model of execution.
In our model, we will assume weak fairness.
26 CHAPTER 2. THE COMPUTATIONAL MODEL
Chapter 3
In this chapter, we address the question: what does a given program do? Often
it is tempting to use operational arguments, that is, arguments that try to
follow all possible executions. Such arguments are cumbersome, however, and
frequently rely heavily on case analysis and long chains of causality. Instead, we
would prefer to answer the question “what does this program do” in a manner
that convinces ourselves (and others) that our answer is correct.
3.1 References
• The best reference for this material is again the classic “Parallel Program
Design: A Foundation”, by K. M. Chandy and J. Misra [CM88]. Our
presentation here, however, is quite different. For example, our basic
temporal operators, transient and next, do not appear in the Chandy &
Misra book. Nevertheless, the fundamental ideas (such as the Assignment
Axiom) are the same.
• For another presentation of transient and next, you can look at Jay
Misra’s book “A Discipline of Multiprogramming” [Mis01]. (The next
operator that we use is introduced as co in this reference.)
3.2 Introduction
3.2.1 Motivational Example
Recall the example given in the previous chapter for finding the maximum ele-
ment in an array.
Program F indM ax
var A : array 0..N − 1 of int,
r : int
initially r = A[0]
27
28 CHAPTER 3. REASONING ABOUT PROGRAMS
assign
( [] x : 0 ≤ x ≤ N − 1 : r := max(r, A[x]) )
[F P ≡ ( ∀ x : 0 ≤ x ≤ N − 1 : r = max(result, A[x]) )]
[F P ≡ r ≥ ( Max x : 0 ≤ x ≤ N − 1 : A[x] )]
But we believe that the program is “correct” and that r is eventually equal
to the maximum, not greater than it.
In this chapter we will formalize our intuition of what it means for a program
to be correct. We will see the tools for proving that our programs are indeed
correct without relying on operational arguments.
3.2.2 Specification
Reasoning about a program involves convincing ourselves, and others, that the
program meets its specification. A specification is some kind of higher-level
description of program behavior. The question is how to write such higher-level
descriptions.
We will use program properties to specify our programs. Recall that non-
determinism is fundamental (even inescapable) in our model of computation.
There are therefore many possible executions (also called “computations” or
“traces”). A program property is a predicate on an execution. We say that a
program property R holds for a program P exactly when R holds for every
possible execution P .
There are two fundamental categories of program properties that we will
use to describe program behavior: safety properties, and progress properties.
Before considering these properties on computations (consisting of an infinite
sequence of actions), we first examine how to reason about the behavior of a
single action, executed once.
{P } S {Q}
3.3. REASONING ABOUT A SINGLE ACTION 29
??? Q
{P } x := y + 1 {even.x}
In order for this to be true, even.x must be true after execution of the assign-
ment. So what must have been true before execution? It must have been the
case that y + 1 was even! For example, {y + 1 = 6} x := y + 1 {even.x}
holds. As does the triple {y + 1 = 0} x := y + 1 {even.x} . Both of these
are represented in Figure 3.2.
Indeed, saying that a triple {P } x := y + 1 {even.x} holds is the same
as saying:
[P ⇒ ]
30 CHAPTER 3. REASONING ABOUT PROGRAMS
y+1 = 6
y+1 = 0
even.x
[x ≥ −2 ⇒ (x − y + 3) + y ≥ 0]
Proof. (x − y + 3) + y ≥ 0
≡
We must prove:
[(x > y = 7 ∧ x > y ⇒ y > 3) ∧ (x > y = 7 ∧ ¬(x > y) ⇒ x > 3)]
Proof. (x > y = 7 ∧ x > y ⇒ y > 3) ∧ (x > y = 7 ∧ ¬(x > y) ⇒ x > 3)
⇐ { antecedent strengthening of ⇒ : [(X ⇒ Z) ⇒ (X ∧ Y ⇒ Z)] }
(y = 7 ⇒ y > 3) ∧ (x > y = 7 ∧ ¬(x > y) ⇒ x > 3)
≡ { 7>3 }
x > y = 7 ∧ ¬(x > y) ⇒ x > 3
≡ { definition of ¬ }
x>y=7 ∧ x≤y ⇒ x>3
⇐
x>y ∧ x≤y ⇒ x>3
≡
false ⇒ x > 3
≡ { property of ⇒ : [false ⇒ X ≡ true] }
true
3.4 Safety
Informally, a safety property says that “nothing bad happens”. One litmus test
for a safety property is that it is a property that can be violated by a finite
computation.
32 CHAPTER 3. REASONING ABOUT PROGRAMS
Example 1. “This pen does not explode”. If we watch the pen for 1 minute,
and it does not explode, safety has not been violated. Conversely, if the pen
were to violate this safety property and explode, could we conclude that it had
exploded after a finite computation? Certainly.
Example 2. Consider a light switch with the property: “When the switch is
off, the light is off too”. Again, if a switch were to violate this property (i.e.,
the light comes on while the switch is off), we could observe this violation after
a finite computation.
3.4.1 Next
Our most basic operator for safety will be next. A next property (i.e., a
predicate on programs) is written:
P next Q
where P and Q are predicates on states in the program. To make explicit the
program to which this property refers, we could write (P next Q).G , where
G is a program. Typically, however, the program is understood from context
and is omitted.
Informally, the property P next Q means that if a program is in a state
satsifying predicate P , its very next state (i.e., after choosing and executing
exactly one action) must satisfy Q .
How could we prove that a program satisfies such a property? Since any
action could be chosen as the next one to be executed, we must show that every
action, if it begins in P , must terminate in Q .
One subtlety is that skip is always part of any program. So, in order to have
P next Q one of the things we will have to show is {P } skip {Q} . What
does this mean about how P and Q are related? .
Figure 3.3 is a graphical representation of a next property.
The predicate Q can be seen as a “constraint” on how far the program can
move out of P in one step. Of course, once outside of P (but possibly still in
Q ) this property says nothing about what must happen next.
As an exercise, consider which of the following theorems are true for next.
3.4. SAFETY 33
Constants.
false next Q
P next true
true next false
Junctivity.
(P1 next Q1 ) ∧ (P2 next Q2 ) ⇒ (P1 ∧ P2 ) next (Q1 ∧ Q2 )
(P1 next Q1 ) ∧ (P2 next Q2 ) ⇒ (P1 ∨ P2 ) next (Q1 ∨ Q2 )
3.4.2 Stable
Stability means that once something becomes true, it remains true. We write
stable.P , where P is a predicate, to indicate that P is stable. If this is a
property of program G , this would be written stable.P.G (recall that function
application associates to the left). Often G is understood from context.
34 CHAPTER 3. REASONING ABOUT PROGRAMS
stable.P ≡ P next P
Again, as an exercise, decide which of the following theorems are valid for
stable.
stable.true
stable.false
stable.P ∧ stable.Q ⇒ stable.(P ∧ Q)
stable.P ∧ stable.Q ⇒ stable.(P ∨ Q)
stable.P ∧ [P ⇒ P 0 ] ⇒ stable.P 0
stable.P ∧ [P 0 ⇒ P ] ⇒ stable.P 0
3.4.3 Invariant
A stable predicate that holds initially is said to be an invariant. That is, this
predicate is true for the entire computation.
For example, consider the FindMax example. Let M be the maximum value
in the array. That is:
invariant.(r ≤ M )
( ∀ x : 0 ≤ x < N : A[x] ≤ M )
1. initially.(r ≤ M )
r = A[0]
⇒ { A[0] ≤ M }
r≤M
3.4. SAFETY 35
2. stable.(r ≤ M )
stable.(r ≤ M )
≡
(r ≤ M ) next (r ≤ M )
≡
( ∀ a :: {r ≤ M } a {r ≤ M } )
≡ { definition of program }
( ∀ x : 0 ≤ x < N : {r ≤ M } r := max(r, A[x]) {r ≤ M } )
≡ { assignment axiom }
(∀x : 0 ≤ x < N : )
≡ { lemma }
(∀x : 0 ≤ x < N : r ≤ M ⇒ r ≤ M )
≡ { predicate calculus }
true
3.4.4 Unless
Informally, P unless Q means that if P is true at some point, it remains true
unless Q becomes true, at which point all bets are off. Conceptually, Q is like
a “gate” through which the program must pass.
Consider Figure 3.4 with predicates P and Q . Complete this figure with
possible state transition arrows. There are 3 areas of the figure which can be
considered separately:
1. P ∧ ¬Q −→
2. ¬P ∧ Q −→
3. P ∧ Q −→
P Q
3.5 Progress
Informally, a progress property says that “something good happens eventually”.
It can be viewed as a predicate on possible computation suffixes. Unlike safety,
it can not be violated by a finite execution. (Progress is also referred to as
“liveness”).
Example 1. “Eventually, this pen will levitate”. We can watch the pen for
some amount of time, say 1 minute. Is it possible to detect after that time
whether or not the property has been violated? No. This is an example of a
progress property.
Example 2. “When the switch is turned on, eventually the light comes on
too.”
We saw that safety properties alone are not enough. We have now introduced
a new class of properties, progress properties. A natural question now might be,
are these two classes of program properties enough? It was shown by Alpern and
Schneider (in Information Processing Letters, 1985) that all program properties
of interest can indeed be expressed as a conjunction of safety and progress
[AS85].
3.5.1 Transient
Our fundamental operator for progress properties is transient. Informally,
transient.P means that if P becomes true at some point in the computation,
it is guaranteed to become false at some later point. (This informal rule is not
quite accurate, as we shall see shortly, but it serves to introduce the notion
of transience.) Thus, if P is transient, it must be false infinitely often in a
computation. (But not vice versa, again as will be seen later.)
Strengthening.
transient.P ∧ [P 0 ⇒ P ] ⇒ transient.P 0
Weakening.
transient.P ∧ [P ⇒ P 0 ] ⇒ transient.P 0
Proof. transient.(x = 2)
≡ { definition of transient }
{x = 2} even.x −→ x := x + 1 {x 6= 2}
≡ { assignment axiom }
(x = 2 ∧ even.x ⇒ (x + 1) 6= 2) ∧ (x = 2 ∧ ¬even.x ⇒ x 6= 2)
≡
38 CHAPTER 3. REASONING ABOUT PROGRAMS
(x = 2 ∧ even.x ⇒ x 6= 1) ∧ (x = 2 ∧ ¬even.x ⇒ x 6= 2)
≡
x = 2 ∧ ¬even.x ⇒ x 6= 2
≡
false ⇒ x 6= 2
≡ { property of ⇒ }
true
transient.(n = 0 ∨ n = 1)
≡ { definition of transient }
{n = 0 ∨ n = 1} n ≤ 2 −→ n := n + 1 {¬(n = 0 ∨ n = 1)}
≡ { distribution of ¬ over ∨ }
{n = 0 ∨ n = 1} n ≤ 2 −→ n := n + 1 {n 6= 0 ∧ n =
6 1}
≡ { assignment axiom }
3.5.2 Ensures
Informally, P ensures Q means that if P holds, it will continue to hold so
long as Q does not hold, and eventually Q does hold. This last requirement
means there is (at least) one action that establishes Q starting from any P
state.
Again, complete Figure 3.6 to capture the intution behind this operator.
P Q
The ensures operator is slightly more general than transient. For example,
consider the program with a single action, even.x −→ x := x + 1 . Prove that
the following is a property of the program:
(x = 2 ∨ x = 6) ensures (x = 3 ∨ x = 7)
1. next property
(x = 2 ∨ x = 6) ∧ x 6= 3 ∧ x 6= 7 next x ∈ {2, 3, 6, 7}
≡
x = 2 ∨ x = 6 next x ∈ {2, 3, 6, 7}
≡ { definition of next and Assignment Axiom }
x = 2 ∨ x = 6 ⇒ x + 1 ∈ {2, 3, 6, 7}
≡
true
2. transient property
transient.(x = 2 ∨ x = 6)
≡ { definition of transient }
{x = 2 ∨ x = 6} even.x −→ x := x + 1 {x 6= 2 ∧ x 6= 6}
Although ensures is slightly higher level than transient, it is still quite low-
level. For example, consider the program with a single action, n ≤ 2 −→ n :=
n + 1 . Does this program satisfy the property:
n = 1 ensures n = 3
3.5.3 Leads-to
Leads-to is perhaps the most commonly used operator in expressing progress
properties. Informally, P ; Q means that if P is true at some point, Q will
be true (at that same or a later point) in the computation. This is quite different
than ensures, which restricts what the computation can do before establishing
Q . With leads-to, the computation can meander in all sorts of directions, so
long as it ends up in Q . Also, notice that if both P and Q are true at a point
in the computation, the leads-to property has been satisfied.
Again, use Figure 3.7 to draw an intuitive representation of the important
aspects of leads-to.
P Q
Constants.
P ; true
false ; P
P ; P
(P ; Q) ∧ [Q ⇒ Q0 ] ⇒ P ; Q0
(P ; Q) ∧ [P 0 ⇒ P ] ⇒ P 0 ; Q
If those were too easy for you, think about the following ones...
Stable Strengthening.
Progress-Safety-Progress (PSP).
Conjunctivity.
(P ; Q) ∧ (P 0 ; Q0 ) ⇒ (P ∧ P 0 ) ; (Q ∧ Q0 )
Notice the difference between leads-to and implication with respect to con-
junctivity.
Formally, the definition of leads-to is given by:
P ensures Q ⇒ P ;Q
(P ; Q) ∧ (Q ; R) ⇒ P ;R
( ∀ i :: Pi ; Q ) ⇒ ( ∃ i :: Pi ) ; Q
transient.P
P ; ¬P
It is possible to write a program that satisfies latter property but does not
satisfy the former! You should find such an example to be sure you understand
this distinction. This distinction means that the two expressions are related by
(equivalence or implication). In particular, the relationship is:
transient.P P ; ¬P
Proof. We carry out this proof in two stages. In the first stage, we show n =
k ∧ n ≤ 2 ensures n = k + 1 . To complete this stage, there are two proof
obligations.
1. (n = k ∧ n ≤ 2 ∧ n 6= k + 1) next ((n = k ∧ n ≤ 2) ∨ n = k + 1)
{n = k ∧ n ≤ 2} n ≤ 2 −→ n := n + 1 {(n = k ∧ n ≤ 2) ∨ n = k + 1}
≡
n=k ∧ n≤2 ⇒ n+1=k+1
≡
true
2. transient.(n = k ∧ n ≤ 2)
42 CHAPTER 3. REASONING ABOUT PROGRAMS
{n = k ∧ n ≤ 2} n ≤ 2 −→ n := n + 1 {n 6= k ∨ n > 2}
≡
n = k ∧ n ≤ 2 ⇒ n + 1 6= k ∨ n + 1 > 2
⇐
n = k ⇒ n + 1 6= k
≡
true
We now proceed with the second stage of the proof, in which we use the result
established above to calculate:
n = k ∧ n ≤ 2 ensures n = k + 1
⇒ { definition of ; }
n=k ∧ n≤2 ; n=k+1
⇒ { one-point rule, for n = 1 and n = 2 }
(n = 1 ; n = 2) ∧ (n = 2 ; n = 3)
⇒ { transitivity of ; }
n=1 ; n=3
3.5.4 Induction
In practice, proving a leads-to property often involves an inductive argument.
That is, it may not be easy to prove directly that P ; Q , so instead we show
that P ; P 0 , where P 0 is “closer” to the goal, Q . The inductive theorem
for leads-to makes this intuition precise. The inductive theorem is based on a
concept that you have seen before in sequential programming: metrics.
Definition 1 (Metric). A metric (or “variant function”) is a function from
the state space to a well-founded set (e.g., the natural numbers).
The well-foundedness of the range means that the value of the function is
bounded below (i.e., can only decrease a finite number of times).
Theorem 10 (Induction for ; ). For a metric M ,
( ∀ m :: P ∧ M = m ; (P ∧ M < m) ∨ Q ) ⇒ P ;Q
( ∀ m :: P ∧ M = m next (P ∧ M ≤ m) ∨ Q )
∧ ( ∀ m :: transient.(P ∧ M = m) )
⇒ P ;Q
( ∀ i, m :: {P ∧ M = m ∧ gi } gi −→ ai {(P ∧ M < m) ∨ Q} )
∧ ( ∀ i :: ¬gi ) ⇒ Q
⇒ P ;Q
( ∀ i, m :: {P ∧ M = m ∧ gi } gi −→ ai {(P ∧ M < m) ∨ F P } )
⇒ P ; FP
4.2 FindMax
We have already seen the FindMax example in earlier chapters. We now present
a formal argument that our intuition about this program is right: the program
is “correct”.
Recall the FindMax program calculates the maximum element in an array
of integers, assigning this maximum value to a variable r . To simplify the
exposition, we define a constant M :
M = ( Max x : 0 ≤ x ≤ N − 1 : A[x] )
true ; r = M
45
46 CHAPTER 4. SMALL EXAMPLE PROGRAMS
stable.(r = M )
Program F indM ax
var A : array 0..N − 1 of int,
r : int
initially r = A[0]
assign
( [] x : 0 ≤ x ≤ N − 1 : r := max(r, A[x]) )
Proof. Fixed Point. We have already calculated the fixed point of this program
as being:
FP ≡ ( ∀ x : 0 ≤ x ≤ N − 1 : r = max(r, A[x]) )
≡ r ≥ ( Max x : 0 ≤ x ≤ N − 1 : A[x] )
≡ r≥M
1. It is guaranteed not to decrease (i.e., the next property for metrics). That
is, we prove r = k next r ≥ k .
{r = k} r := max(r, A[x]) {r ≥ k}
≡ { assignment axiom }
r = k ⇒ max (r, A[x]) ≥ k
≡ { property of max }
r = k ⇒ r ≥ k ∨ A[x] ≥ k
≡
true
2. It is bounded above. This follows directly from the invariant property that
shows that it is bounded above by M .
transient.(r = k ∧ r < M )
≡ { definition of transient }
( ∃ a :: {r = k ∧ r < M } a {r 6= k ∨ r ≥ M } )
⇐ { definition of program }
{r = k ∧ r < M } r := max(r, A[m]) {r 6= k ∨ r ≥ M }
≡ { assignment axiom }
r = k ∧ r < M ⇒ max (r, A[m]) 6= k ∨ max (r, A[m]) ≥ M
⇐ { weakening antecedent }
r = k ∧ r < M ⇒ max (r, A[m]) 6= k
≡ { definition of m }
r = k ∧ r < M ⇒ max (r, M ) 6= k
⇐
M > k ⇒ max (r, M ) 6= k
⇐ { weakening antecedent }
M > k ⇒ max (r, M ) > k
≡ { property of max }
true
With this metric, we can apply induction to establish that the program
terminates. That is:
true
≡ { transientproperty established above }
transient.(r = k ∧ r < M )
⇒ { transient.P ⇒ (P ; ¬P ) }
r = k ∧ r < M ; r 6= k ∨ r ≥ M
⇒ { stable.(r ≥ k) }
r=k ∧ r<M ; r>k ∨ r≥M
≡ { [X ∨ Y ≡ (¬Y ∧ X) ∨ Y ] }
r < M ∧ r = k ; (r < M ∧ r > k) ∨ r ≥ M
⇒ { induction }
r<M ; r≥M
≡ { definition of F P }
r < M ; FP
≡ { initially.(r < M ) }
true ; F P
4.3 Sorting
In this example, we consider sorting an array of integers. The specification for
this program says that at termination, the array is in nondescending order and is
48 CHAPTER 4. SMALL EXAMPLE PROGRAMS
a permutation of the original array. It also requires that the program eventually
terminate.
Consider the following program:
Program Sort
var A : array 0..N − 1 of int
assign
( [] i, j : 0 ≤ i < j < N : A[i] > A[j] −→ swap(A, i, j) )
Clearly this metric is bounded below (by 0). It is not clear, however, that the
metric can only decrease. To see this, consider the effect of a single swap that
swaps elements X and Y . These two elements divide the array into thirds:
Since the action swaps elements X and Y , it must be the case that X > Y .
Therefore, after the swap, this pair is no longer out-of-order. But is it possible
for the swap to cause other pairs, that used to be in order to now be out of
order?
Consider the different cases for other pairs that are in order before the swap.
We show that they are still in order after the swap.
1. Neither element of the pair is X or Y . The swap does not affect this
pair.
4.4. EARLIEST MEETING TIME 49
2. One element of the pair is in part a of the array. Again, the swap does
not affect the number of out-of-order pairs for this element (since both
swapped elements are to its right.)
3. One element of the pair is in part c of the array. The swap does not affect
the number of out of order pairs for this element for the same reason as
the previous case.
4. One element of the pair is in part b . If it was in-order with respect to X
before the swap (i.e., c > X ), it will be in-order with respect to Y after
the swap, since X > Y . Similarly, if it was in-order with respect to Y
before the swap, it will be in-order with respect to X after the swap.
Therefore the number of out-of-order pairs can never increase.
It is also easy to see that any action that performs a swap decreases the
metric by at least one (the pair that was swapped is now in-order). Therefore,
every enabled action decreases the metric. Therefore, the program terminates.
true ; r = M
stable.(r = M )
Program EM T
var r : time
initially r=0
assign
r := f.r
[] r := g.r
[] r := h.r
Therefore, r ≥ M .
Invariant.
invariant.(r ≤ M )
Therefore, at termination, we have r = M .
Metric. As a metric, we use r . It is guaranteed not to decrease, since
f.t ≥ t , by definition of f . It is also bounded above by M (as given by the
invariant). To see that it is guaranteed to change if it is below M , consider any
value for r , where r < M . For this value, there is a professor (say F ) who is
not available (otherwise all professors would be available and this would be the
minimum time!) Therefore f.r > r so the action r := f.r increases the metric.
Therefore, the program terminates.
true ; x = y = gcd(X, Y )
stable.(x = y = gcd(X, Y )
Program GCD
var x, y : int
initially x>0 ∧ y>0 ∧ x=X ∧ y=Y
assign
x > y −→ x := x − y
[] y > x −→ y := y − x
FP
≡
(x > y ⇒ x = x − y) ∧ (y > x ⇒ y = y − x)
≡
(x > y ⇒ y = 0) ∧ (y > x ⇒ x = 0)
Invariant.
5.1 References
1. Logical time (and the “happens-before” relation) is defined in Lamport’s
article “Time, Clocks, and the Ordering of Events in Distributed Sys-
tems”, CACM 21(7) p.558–565, 1978 [Lam78]. This article also discusses
the synchronization of physical clocks and presents an algorithm for ac-
complishing this synchronization and the bound on the resulting error,
assuming message delay is bounded.
3. The Singhal and Shivaratri book contains a good discussion of both logical
time and vector clocks [SS94].
5.2 Introduction
The concept of time is pervasive. When we think of an event, we typically think
of it having occured at a particular time. The granularity with which we view
time might vary depending on the event. For example, Christofero Colombo
sailed across the Atlantic in 1492. Paul Henderson scored the winning goal for
Canada in the Canada Cup hockey tournament on September 28, 1972. The
53
54 CHAPTER 5. TIME, CLOCKS, AND SYNCHRONIZATION
3. There is some third event C , which A can affect, which in turn can affect
B.
A k B = ∧
(A k B) ∧ (B k C) ⇒ (A k C)
5.3.2 Timelines
During a computation, events occur on different processes at various times. One
way to visualize a computation is to draw what is sometimes known as a “time-
line” or “space-time” diagram. In such a diagram, the events are represented
by vertices and the “happens-before” relation is included as directed edges be-
tween the vertices. Furthermore, the diagram is structured so that all events
occurring on process Pi are arranged in a horizontal line, with the computation
proceeding towards the right.
For example, Figure 5.1 shows a possible timeline for a computation with
four processes. Notice how the labels for the individual events are omitted
and the happens-before relation on the same process is understood. The edges
between horizontal lines represent .
Notice that in this diagram, all the arrows point towards the right. That is,
a horizontal axis of “true time” is used and events are placed according to when
they actually occur. An important consequence of this observation is that there
are no cycles in the directed graph.
More generally, if “true time” is not used to place the events (since there is
no way to calculate what the “true time” of an event is anyways!), a directed
graph results. Such a graph still has horizontal lines of events that occur on
the same process, but now arrows between horizontal lines can point to the left.
For example, the timeline given in Figure 5.2 is equivalent to the one given
previously in Figure 5.1. The key property of timelines, therefore, is that they
do not contain . Indeed, the happens-before relation forms a
partial order on the events of the computation.
56 CHAPTER 5. TIME, CLOCKS, AND SYNCHRONIZATION
P4
P3
P2
P1
"time"
P4
P3
P2
P1
5.3.4 Algorithm
The desired property suggests the algorithm. It is an invariant we wish to
maintain as new events are generated. That is, for each new event that occurs,
we must assign it a timestamp such that its timestamp is guaranteed to be
greater than the timestamps of any events that “happened-before”. There are 3
cases to consider for possible events:
1. Local event. The clock is incremented and this updated value is the
timestamp of the new event.
clock := clock + 1
; time.A := clock
2. Send event. The clock is incremented and this updated value is the
timestamp of the new event and of the message being sent.
clock := clock + 1
; time.A, time.m := clock, clock
Notice that the invariant maintained with respect to clock is that it is equal to
the most recently assigned timestamp.
(Aside: There are many equivalent ways of writing these assignments. The
sequential composition of assignments above with ; is not strictly necessary. It
is possible to rewrite each of these sequences of assignments as a single multiple
assignment. Such a rewrite is not simply a matter of replacing the ; with a k ,
but it is possible. This presentation was chosen for clarity and convenience.)
As an exercise, consider the timeline in Figure 5.3. Assign logical timestamps
to the events (indicated by circles) in accordance with the algorithm above.
Initially, the clocks begin at the values indicated.
The algorithm can be written as a program in our action system model. For
a single process ( j ) the algorithm is:
Program LogicalClock j
var j, k : processes,
ch.j.k : channel from j to k ,
58 CHAPTER 5. TIME, CLOCKS, AND SYNCHRONIZATION
P3
2
P2
1
P1
8
"time"
m : message,
A, B : events,
clock.j : logical time of j ,
time.A : logical time of A ,
initially clock.j = 0
assign
local event A −→ clock.j := clock.j + 1
; time.A := clock.j
[] send event A −→ clock.j := clock.j + 1
(to k ) ; time.A, time.m := clock.j, clock.j
; ch.j.k := ch.j.k | m
[] rcv event A −→ clock.j := max (time.m, clock.j) + 1
( m from k ) ; time.A, ch.j.k := clock.j, tail(ch.j.k)
x1 , x2 , x3 , ...
In particular, for events and the “happens-before” relation, we can write the
list of events as
e1 , e2 , e3 , ..., ei , ...
such that for all i , j , if ei “happens-before” ej , ei comes before ej in the
list.
The timeline diagrams represent the partial order of events and the assign-
ment of logical times to events gives us one way to globally order them. We
can write a totally ordered sequence of events using logical time by writing the
events in .
B
P1
9
"time"
The source of the problem is that a single integer does not capture enough
information. We do not know whether event B received its timestamp because
of a chain of local events or because of a chain of messages after event A . In
general, an event can receive a timestamp n either because the previous local
event had timestemp n − 1 or because it is a receive event for a message with
timestamp n − 1 . (Of course both reasons could be true). Just the value of the
integer alone, however, does not capture which “happens-before” relation forced
the assignment of this timestamp to the event.
The solution, then, is to not throw this information away. This leads to
the development of a new timestamping scheme, one known as vector clocks.
To distinguish vector and logical clocks, we will use vtime and vclock for the
vector quantities. The benefit of the vector clock scheme is that it has the
property:
A −→ B ≡ vtime.A < vtime.B
P3
6 2 9 3
"time"
The algorithm for timestamping events and updating the local clock remains,
in essence, the same:
• A clock is maintained by each process, representing the timestamp last
assigned to an event occurring at that process.
• Each message sent is timestamped with that process’ current time (i.e., a
vector of integers).
• When a local event occurs, the clock must be incremented to assign a time
to this local event. (Question: How?)
• When a receive event occurs, it must be timestamped appropriately (i.e.,
with a value that is greater than the last local event and greater than the
corresponding send event). (Question: How?)
5.4. VECTOR CLOCKS 61
The only issue to be resolved is how to perform the clock updates. Consider
the update that must occur when a receive occurs. In Figure 5.6 event C is a
receive event and event A is the corresponding send.
6 2 9 3
P3
A
B C
P2
8 6 2 1 ? ? ? ?
"time"
Therefore, we need to decide what it means for one vector to be less than ( < )
another. We define this as .
That is, the formal definition of the ordering of two vectors time.A and time.B
is given by:
example, we could increase only one component or increase all the components.
Intuitively, if we are haphazard with how the vector is increased (e.g., increasing
all the components by various amounts) there is the same loss of information as
we saw with logical clocks and we won’t have the desired equivalence property.
One way to think of process j ’s vector clock is as a list of the most recent
news from all the processes in the computation (including itself). It follows that
we expect an invariant of the implementation to be:
( ∀ j, k :: vclock.k.j ≤ vclock.j.j )
5.4.3 Algorithm
The algorithm for updating vector clocks is analagous to that for logical clocks.
The only part to which we have to pay special attention is how to increment
the clock when processing a receive event. Again, there are 3 cases to consider
for possible events:
1. Local event. The clock component corresponding to the process is incre-
mented and the updated clock value is the timestamp of the new event.
vclock.j := vclock.j + 1
; vtime.A := vclock
vclock.j := vclock.j + 1
; vtime.A, vtime.m := vclock, vclock
Again, the invariant maintained with respect to vclock is that it is equal to the
most recently assigned timestamp.
As an exercise, consider the timeline in Figure 5.7. Assign vector timestamps
to the events (indicated by circles) in accordance with the algorithm above.
Initially, the clocks begin at the values indicated.
5.4. VECTOR CLOCKS 63
0 0 1
P3
P2
0 1 0
P1
1 0 0
The algorithm can be written as a program in our action system model. For
a single process ( j ) the algorithm is:
Program V ectorClock j
var j, k : processes,
ch.j.k : channel from j to k ,
m : message,
A : event,
vclock.j : vector time of j ,
vtime.A : vector time of A ,
initially vclock.j.j = 1
∧ ( ∀ k : k 6= j : vclock.j.k = 0 )
assign
local event A −→ vclock.j.j := clock.j.j + 1
; vtime.A := vclock.j
[] send event A −→ vclock.j.j := vclock.j.j + 1
(to k ) ; vtime.A, vtime.m := vclock.j, vclock.j
; ch.j.k := ch.j.k | m
[] rcv event A −→ vclock.j := max (vtime.m, vclock.j)
( m from k ) ; vclock.j.j := vclock.j.j + 1
; vtime.A, ch.j.k := vclock.j, tail(ch.j.k)
( ∀ j, k :: vclock.k.j ≤ vclock.j.j )
∧ ( ∀ j, k, mj :: vtime.mj .k ≤ vclock.j.k )
∧ ( ∀ Aj , Bk :: Aj −→ Bk ≡ vtime.Aj < vtime.Bk )
∧ ( ∀ Aj , Bk :: Aj −→ Bk ⇐ vtime.Aj .j ≤ vtime.Bk .j )
64 CHAPTER 5. TIME, CLOCKS, AND SYNCHRONIZATION
The second last conjunct in this invariant is the desired equivalence property
that we set out to establish. By examining the timestamps of two events, we
can determine whether one happened before the other (or whether they were
concurrent).
Notice also the last conjunct in the invariant. It gives a “short-cut” for
determining whether there is a happens before relation between two events. It
states that the entire vectors do not need to be compared. The only way for the
j component of event Bk ’s timestamp to be greater than the j component of
event Aj ’s timestamp is for there to be a message chain from Aj to Bk . That
is, Aj −→ Bk .
For example, in Figure 5.8 the partial vector clocks for two events are given.
Event A is on process 2, while event B is on process 3. Because vtime.B.2 is
greater than (or equal to) vtime.A.2 , there must be a chain of messages from
A to B . That is, we can conclude from only this partial information that
A −→ B . The clock updating scheme ensures that these partial values are
only possible when A −→ B . (Which, in turn, means that the other entries of
these vector timestamps must satisfy the ≤ ordering.)
P3
B
A
P2
"time"
but it can not be used to tell time (e.g., is it 11:30 yet?) until it has been
synchronized with “true time”. Even after having been synchronized, real clocks
do not tick perfectly accurately and can “drift” over time. To correct this, the
clock must by synchronized again. (The more accurate the ticking of the clock,
the less often such resynchronizations are needed.)
In this section, we discuss clock synchronization schemes first under the
assumption that messages are transmitted with arbitrary delay, then under the
assumption that message delay is bounded by some fixed value.
3:00pm
T
x
P
Request-Reply
The reason that the assertion at point x is so weak is that P has no way
of knowing the delay in the transmission of the message from T . To improve
on this situation, P must be able to bound this delay. One solution, is to
introduce a protocol between P and T whereby P first sends a message to
T , then waits for the reply (containing the current time at T ). Since P can
time the interval between its first message and receiving the reply, an upper
bound is known on the delay incurred by T ’s message. This is illustrated in
Figure 5.10.
3:00pm
T
x
P
10 min
Figure 5.10: Request and reply protocol for obtaining current time.
66 CHAPTER 5. TIME, CLOCKS, AND SYNCHRONIZATION
Multiple Requests
If more accuracy is required, the protocol can be repeated in the hope of nar-
rowing the interval. Of course, if the delays on the messages (both outgoing
and incoming) are exactly the same, there is no improvement. If, however, the
delays vary, the possible interval narrows.
For example, consider P immediately sending another request to T . This
time the round-trip delay for a reply (received at point y is 12 minutes). P
would expect the current time at T to be in the interval [3:12 .. 3:22] . The
3:00pm 3:20pm
T
x y
P
10 min 12 min
Figure 5.11: Multiple requests and replies for obtaining current time.
time value received in this second reply, however, is 3:20. Therefore, the current
time at T must be in the interval [3:20 .. 3:32] (by the same calculation as
before). In order for both of these to be correct, the current time at T (at point
y ) must be in their intersection, or the interval [ .. ] . Notice
that this resulting interval is tighter than either of the previous two!
Another way to get a similar effect is to simultaneously send requests for
current time to multiple servers. For example, P might send requests to servers
T1 , T2 , T3 , and T4 . Once a reply has been received from all of these servers,
the appropriate intervals for each are known. Assuming all servers are accurate
(i.e., are synchronized with “true time”), then the intersection of these intervals
can be taken as before. See Figure 5.12.
Again, notice in Figure 5.12 that the resultant interval is narrower (i.e.,
higher accuracy) than any one individual interval.
This analysis is based on the assumption that all Ti are accurate. This is a
big assumption! In general, the Ti will not themselves be perfectly synchronized
with “true time”. This means that the intersection of all the intervals might be
empty.
Therefore, instead of taking the intersection over all intervals, we can view
each interval as a vote for the possibility of the true time lying in that interval.
These votes can be tallied to determine a likely interval (or set of intervals).
5.5. SYNCHRONIZATION OF PHYSICAL CLOCKS 67
3:00 3:10
T1
2:55 3:08
T2
2:58 3:06
T3
3:03 3:15
T4
{
Figure 5.12: Multiple simultaneous requests for obtaining current time.
count
4
3
2
1
time
In general, this graph (i.e., the calculated tally) could have virtually any
pattern of increments and decrements to the count. (Subject to the constraint,
of course, that the count is always non-negative and is 0 to the left and right.)
For example, Figure 5.14 is a possible result of the collection of several intervals.
68 CHAPTER 5. TIME, CLOCKS, AND SYNCHRONIZATION
The information contained in such a tally can be used in various ways. For
example, the interval(s) with the highest count could be used. Alternatively, an
apriori decision might be made on the greatest number of faulty servers. Then,
any interval that exceeds the corresponding threshold (e.g., has a count of at
least 3) is part of the solution interval.
The bound κ is very small (certainly much less than 1) and represents an upper
bound on the amount of “wobble”.
This bound means that after a period of time, τ , two clocks (each drifting
in opposite directions) can be out of synchrony with each other by a total of
2κτ .
A further assumption is made that message delay is known to lie within a
particular interval. If the minimum delay is n and the maximum delay is m ,
then this helps bound the size of the uncertainty interval when a timestamped
message is received. For example, a process receiving a message from T times-
tamped with a value t knows that the current time at T is in the interval
[t + n .. t + m] . The size of this interval, then, is given by m − n . Call this
5.5. SYNCHRONIZATION OF PHYSICAL CLOCKS 69
Ci
time
size ζ . Finally, if the diameter of the graph (i.e., the minimum number of
hops required for any process to communicate with any other) is d , then the
synchronization error ( ) is bounded by
≤ d(2κτ + ζ)
70 CHAPTER 5. TIME, CLOCKS, AND SYNCHRONIZATION
Chapter 6
Diffusing Computations
(Gossip)
6.1 References
A nice, brief, and seminal reference for synchronization using diffusing compu-
tations is Dijkstra and Scholten’s paper “Termination Detection for Diffusing
Computations”, in Information Processing Letters, 11(1), p. 1–4, 1980 [DS80].
6.2 Introduction
The term “diffusing computation” refers to a computation that begins at a single
process and spreads out to include the entire set of processes. This is also known
as “gossip” because the way the diffusion occurs is similar to the way gossip
travels among people: one person tells everyone they know, then these people
tell everyone they know, and so on, and so on, ...
In this chapter, we will look at a particular instance of this larger class of
algorithms. We will examine how gossip can be used for barrier synchronization
of a collection of processes.
One application for barrier synchronization of this kind is in rolling over
sequence numbers. For example, with logical time the counters used to times-
tamp events must constantly increase. In a real implementation, however, these
counters are bounded by some maximum value. It is therefore necessary to
periodically rollover and start reusing small values of the counter. In order to
start using the low counter values, a process must notify all other processes of
its intention to rollover the counter and it must know that these processes have
received this notification.
In general, a barrier synchronization is a point at which processes wait until
all processes have reached the barrier before proceeding. See Figure 6.1. The
key property is that after the synchronization point, a process knows that all
71
72 CHAPTER 6. DIFFUSING COMPUTATIONS (GOSSIP)
Synchronization
Point
P1
using old sequence assert: all other processes
numbers have reached (or crossed)
the synchronization point
The computation spreads by “diffusion” (if you are a chemist) or “gossip” (if
you are sociologist). When a process first hears the gossip it must:
This is a spreading phase, in which more and more processes are included in the
gossip. It is easy to see that eventually all processes will have heard the gossip
(assuming the graph is connected).
This, however, is only half the problem. In order to have a barrier synchro-
nization, the initiator must know when all the processes have been included in
the gossip. This suggests that we need not only an expanding phase, but a
constricting phase as well!
6.3. OPERATIONAL VIEW 73
=>
>
=> >=>=>= 999 :9:99
:
:
I I
=
:
:
< <;;<
43
$
#$ $#$$# &% &% ;
6 8 8778
4
# &%&% &%&% <; 5
5 656565
87 3
I I
!"
"
!! ! " ! "
$ #
# ; <; 6
< 56 7
7 87 4
8 34
3 4343
""
!"
!" ! " "!"!"!
x y x y
!"! !
"!""!"!
"! @?@?@? @?@?@?
I I +,
,
+ ,+,+,+ 12
2
1 I 212121
+
,
1
2
x y x y
*)
/
0
0
0/
)*) *)*)*) (
* '( ' ('('('
'( / 0/0/ .
/0 -. - .-.-.-
-.
gossip
ack x y x y x y
From x ’s point of view, both cases are identical. Yet in one case the message
it is receiving is a gossip (i.e., y has set x to be its child), while in the other
case it is receiving an acknowledgement (i.e., y has set x to be its parent).
Put another way, the “problem” is that processes that x considers to be its
children might not consider x to be their parent!
Convincing ourselves that this algorithm is correct, therefore, requires some
careful thought. An operational view might give us some intuition, but it is hard
to argue convincingly about the correctness of this algorithm with operational
traces. Instead, we take a more assertional approach.
74 CHAPTER 6. DIFFUSING COMPUTATIONS (GOSSIP)
6.4 Specification
The first part of any argument of correctness must be a specification. For this
purpose, we begin with some definitions:
6.5 Algorithm
A process can be in one of three states: idle , active , and complete . Ini-
tially processes are idle . Once they hear the gossip (and pass it along to their
children), they become active . Once they hear acknowledgements from their
children (and acknowledge their parent), they become complete .
For a (non-initiator) process u :
Program Gossip u
var parentu : process,
stateu : {idle, active, complete},
msg(a, b) : channel from a to b ,
initially idle
∧ ( ∀ v : u nbr v : ¬msg(u, v) )
assign
( [] v : v nbr u : idle ∧ msg(v, u) −→
parentu := v
k ( k w : w nbr u ∧ w 6= v : msg(u, w) := true )
k stateu := active )
[] active ∧ ( ∀ v : v nbr u ∧ v 6= parentu : msg(v, u) ) −→
msg(u, parentu ) := true
k stateu := complete
6.6.1 Safety
First, define two directed graphs. Each graph is defined by its set of vertices
and its set of edges.
def
T1 = vertices: set of active or complete nodes + Initiator
edges: < u, parentu > for all nodes in graph
def
T2 = vertices: set of active nodes + Initiator
edges: < u, parentu > for all nodes in graph
As an aside, note that T2 ⊆ T1 .
There are two key invariants:
1. T1 is a . (It only grows.)
2. T2 is a . (It grows then shrinks.)
Other invariants that are helpful include:
msg(u, v) ⇒ u ∈ T1
u.complete ⇒ ( ∀ v : < v, u >∈ T1 : v.complete )
Now we show that the two key invariants hold for this program.
T1 is a tree.
Proof. First observe that it is clearly true initially, since initially T1 consists of
a single node, the Initiator, and no edges.
Next we observe that edges and vertices are never removed from T1 . There is
a single action that can add edges and vertices, and that is action A1 . Consider
when A1 adds a vertex u to the graph. If action A1 is enabled, it means
idle.u ∧ msg(v, u) . This means that v is in T1 (from the helpful invariant).
So adding a vertex u and an edge < u, v > is ok (i.e., we are adding a leaf,
which keeps it a tree).
T2 is a tree.
Proof. The proof is similar to the above argument for why it is true initially
and why it is preserved by action A1 .
The difference here is that T2 can shrink. That is, vertices can be deleted
(by action A2 ). In order for such a deletion to preserve the invariant, we must
ensure that only are removed.
We argue this by contradiction. That is, assume that the u that is removed
(by becoming complete ) is not a leaf. Therefore, there exists an n in T2 that
has u as its parent. But for A2 to be enabled, there must be msg(n, u) (since
n can not be u ’s parent). Therefore, n has sent a message to its parent and
so must be complete . This means, however, that n is not in T2 as assumed.
This is a contradition, and hence u must be a leaf.
76 CHAPTER 6. DIFFUSING COMPUTATIONS (GOSSIP)
6.6.2 Progress
We show progress by finding a metric (in this case, an increasing metric).
Proof. As a metric, we choose (#complete, #active) . This is a pair whose first
element is the size of T1 − T2 and whose second element is the size of T2 . This
metric is ordered lexicographically. 1
This metric is bounded above (since the graph is finite). It is also guaranteed
not to decrease, since stable.(complete.u) and a node stops being active (i.e.,
the second component decreases) only by becoming complete (i.e., the first
component increases).
Now it remains to show that the metric increases eventually. That is,
( ∃ v :: ¬complete.v ) ∧ M = m ; M > m
Mutual Exclusion
7.1 Reference
Chapter 6 of the Singhal and Shivaratri book [SS94] is an excellant reference for
this material.
7.2 Introduction
A distributed collection of processes share some common resource. They require,
however, mutually exclusive access to this shared resource. Our task is to design
a program that acts as a “mutual exclusion layer” that resolves the conflict
between processes competing for the shared resource. Figure 7.1 is a basic
sketch of this architecture.
U1
U2 Mutual Exlcusion
Layer
U3
U4 ...
Figure 7.1: Mutual exclusion layer arbitrates user process conflicts
77
78 CHAPTER 7. MUTUAL EXCLUSION
user
NC TRY CS
The requirement that the user process conform to this state transition dia-
gram can stated more formally as:
N C next N C ∨ T RY
stable.T RY
CS next CS ∨ N C
Note that these are requirements on the user process only. They will not nec-
essarily be properties of the mutual exclusion layer and hence not be properties
of the final, composed, system.
In addition, we place one more important requirement on the user process:
the critical section must be finite. That is:
transient.CS
We also fix a protocol to be used between the user processes and the mu-
tual exclusion layer. The user process sends a message “try” when it wishes
to access the shared resource (corresponding to entering its “try” state). The
mutual exclusion layer replies with a message “grant” when access to the shared
resource has been given. Finally, the user process sends a message “exit” when
it is releasing the shared resource and returning to the noncritical state. This
protocol is summarized in Figure 7.3
Our task is to design a mutual exclusion layer that does this job. The first
step, of course, is to formalize what we mean by “this job”.
7.3 Specification
Safety. The safety requirement states that no 2 users are in their critical
section at the same time.
try
grant Mutual Exlcusion
Ui exit Layer
Figure 7.3: Protocol between user process and mutual exclusion layer
Ui Pi
Pj
x 0 0
dbl
U2
U1
inc
x 0 1
7.4.2 Algorithm
Each process keeps as part of its state the following:
• copy of x ,
• logical clock,
• queue of “modify requests” (with their logical time stamps), and
• list of “known times”, one for each other process.
A process executes a modification request when:
• the request has the minimum logical time of all requests, and
• all “known times” are later than the time of the request.
The example given earlier in Figure 7.5 is repeated in Figure 7.6 with this
algorithm. The value of x is initially 0 and the logical clock of U1 is 1 while
the logical clock of U2 is 6.
x 0 0 1
reqQ − dbl,7 dbl,7
time 6 7 10
U2
U1
x 0 0 1
reqQ − inc,2 dbl,7
time 1 2 8
One potential problem with this approach is obtaining a “known time” from
all the other processes. What if there is no “known time” for some Ui ? For
example, what if Ui never issues a request to update x ? To solve this difficulty,
we need a protocol whereby the time from Ui is obtained (regardless of whether
it wishes to issue its own update request for x ). Either the time can be explicitly
requested from Ui or we can use a system of acknowledgements to make sure
that recent time values are known.
As an aside, this approach is based on the assumption that logical time can
indeed be totally ordered. That is, there are no “ties”. This can be done by
making use of a common assumption, namely that processes have unique id’s,
to break any ties.
82 CHAPTER 7. MUTUAL EXCLUSION
Proof of Correctness
Safety.
Proof. The proof proceeds by contradiction. Assume Pa and Pb are both in
their CS . Therefore, both Pa and Pb have their own requests at the head of
their (sorted) reqQ . So the head of Pa .reqQ is < reqa , ta > , while the head
of Pb .reqQ is < reqb , tb > .
Assume, wlog1 , that ta < tb . But, since Pb is in its CS , it must be the
case that tb < Pb .knownT [a] . Hence, reqa (with its timestamp of ta ) must
be in Pb .reqQ (assuming messages are FIFO). Hence reqb is not at the head
of Pb .reqQ .
1 without loss of generality
7.5. NONTOKEN-BASED SOLUTIONS 83
Progress.
Proof. As a metric for a particular process Pi , use the number of entries in
Pi .knownT that are less than its request time ( reqi .t ). Clearly this is bounded
below (by 0). Also, since logical time is monotonically increasing, it never
increases.
To see that this metric is guaranteed to decrease, consider a process Pj with
an entry less than Pi ’s request time. That is:
7.5.2 Optimization #1
As an optimization, notice that not all acknowledgements are required. In par-
ticular, if a request has already been sent with a later timestamp than a received
request, the request received does not need to be acknowledged. The acknowl-
edgement is required to guarantee a known time for this process that is greater
than the request time. But the request already sent can serve as such an ac-
knowledgement!
For example, in Figure 7.7, process Pi does not need to send an acknowl-
edgement for the most recent request (from process Pj ).
No ack needed !!
Pi
(a) Pi is in CS , or
(b) Pi is in T RY and tj > ti of reqi .
A process is allowed to enter the critical section only if it holds the token.
This simple rule guarantees the safety part of the specification!
For progress, we must guarantee that every process that enters T RY even-
tually receives the token. There are several different algorithms to guarantee
this. We examine them in increasing order of generality.
Algorithm
To use resource:
hungry := true
When token arrives:
if hungry
[use resource]
send token on (CW)
Key point: Tokens circulate one way and requests circulate in the opposite
direction.
In Figure 7.10, tokens circulate CW while requests circulate counter-clockwise
(CCW).
Notice that after a process has sent a request (either on its own user pro-
cess’s behalf or forwarded from a neighbor) there is no need for it to send any
subsequent requests!
86 CHAPTER 7. MUTUAL EXCLUSION
requests
Algorithm
else
send token on (CW)
pending requests := false
Some key points to keep in mind with respect to this algorithm are the
following.
• A process forwards at most one request.
• Every process “knows” where the token is in the system (i.e., somewhere
to its right).
• Requests are sent towards the token.
• The token travels along the same path as requests, but in the opposite
direction.
Concerns Although this algorithm addresses the concern of the previous so-
lution (i.e., when there is no demand the token does not circulate), it does have
one serious limitation. Namely, it requires .
There is one potential problem to deal with. In the token ring, a process
with pending requests knew where to send the token once it arrived. That is,
it would simply send it along CW. With a tree, however, when a process receives
the token, to which neighbor (child) should it be sent? Infact, there could be
pending requests from multiple children!
The solution to this problem is to maintain .
88 CHAPTER 7. MUTUAL EXCLUSION
req
req
req
1 2 3 1 2 3 1 2 3
Concern One potential concern with this algorithm is that the path followed
by the token (and requests) is limited to the edges of a spanning tree. The
actual graph, however, may contain considerably more edges that could shorten
the distance needed for the token to travel to reach its destination. The next
solution allows us to exploit these edges by making use of the entire topology.
• Process sends only one request (may need to maintain a list of pending
requests).
7.6. TOKEN-BASED SOLUTIONS 89
Dining Philosophers
8.1 References
1. The dining philosophers problem first appeared in a paper by Dijkstra
in 1971 [Dij71]. It has become a classic synchronization problem and its
many solutions are discussed in many references.
8.2 Introduction
In the initial formulation of the dining philosophers problem, five philosophers
are sitting around a table. Between each philosopher is a single fork and, in
order to eat, a philosopher must hold both forks.
As in the formulation of the mutual exclusion problem, philosophers cycle
between 3 states: thinking, hungry, and eating. Again, the philosopher controls
its own transitions from thinking to hungry and from eating to thinking. On
the other hand, the conflict-resolution layer controls transitions from hungry to
eating.
philo
thinking hungry eating
91
92 CHAPTER 8. DINING PHILOSOPHERS
Again we require that philosophers eat for a finite time. Philosophers may
think for an arbitrary amount of time (potentially unbounded).
As a generalization of the classic formulation, we extend the notion of neigh-
bor. Instead of five philosophers around a table, we consider an arbitrary
(but finite) undirected graph. Vertices in the graph represent philosophers and
edges define neighbors (we require that the graph not have any self loops).
The classic formulation can be seen as an instance of this generalization with
. Similarly, the mutual exclusion problem con-
sidered in the previous chapter can be seen as an instance of the generalized
dining philosophers problem with .
8.3 Specification
We wish to design a “conflict resolution layer” that guarantees the required
specification:
u.t : u is thinking
u.h : u is hungry
u.e : u is eating
For each pair of philosophers, u and v , we also have a boolean E(u, v) . This
boolean is true exactly when there is an edge between u and v . That is:
safety:
progress:
1. Conflicts for a shared resource (i.e., the fork) are resolved in favor of the
process with higher priority.
2. After a process “wins” a conflict (and gets to eat), its neighbors should
be given a chance to win the next round. So the priority of the winner is
lowered.
In order to ensure this mutual exclusion between neighbors, we use the notion
of a token. We introduce one token for each edge (and call this token a “fork”).
To eat, a philosopher must hold all of its forks.
We can prove that this property implies the original specification, so this is
indeed a refinement.
This refinement does not, however, address the issue of priority. So we refine
the specification further.
Complete Figure 8.3 to illustrate the two scenarios in which v could have
higher priority than u .
v v
OR
u u
Some important properties of clean and dirty forks are the following:
1. An eating process holds all its forks and the forks are dirty.
3. A dirty fork remains dirty until it is sent from one process to another (at
which point it is cleaned).2
1. the fork is at u
2.
3.
4.
8.7 Algorithm
In the previous section, the original specification was refined with a series of
new specifications. Note that each specification in the series was tighter or
stronger than the previous. With each successive refinement, a new mechanism
was introduced that was more easily implementable. With the final refinement,
it is easy to see how to write a program that satisfies this specification (and
hence also satisfies the original).
8.7. ALGORITHM 97
Program hygienic
initially p.state = thinking
( ∀ q : E(p, q) : clean(p, q) = false )
Priorities form a partial order
always p.t ≡ p.state = thinking
p.h ≡ p.state = hungry
p.e ≡ p.state = eating
assign
[Hp ] p.h ∧ f ork(p, q) = q
−→ req(p, q) := q;
[Ep ] p.h ∧ ( ∀ q : E(p, q) : f ork(p, q) = p ∧ (clean(p, q) ∨ req(p, q) = q) )
−→ p.state := eating;
clean(p, q) := false;
[Rp ] req(p, q) = p ∧ f ork(p, q) = p ∧ ¬clean(p, q) ∧ ¬p.e
−→ f ork(p, q) := q;
clean(p, q) := ¬clean(p, q);
philosopher {
while (true)
/* state == thinking */
repeat
/* thinking philo holds only dirty forks
recv (m)
switch (m)
case (req from neighbor i)
/*assert fork[i] = T; clean[i] = F*/
fork[i] = F
98 CHAPTER 8. DINING PHILOSOPHERS
fork_ct --
send (fork) to neighbor i
case (became_hungry)
state = hungry
until (state == hungry)
state = eating
for (i < num_neigh; i = 0; i ++)
clean[i] = F
repeat
recv(m)
switch (m)
case (req from neighbor i)
request[i] = T
case (done_eating)
state = thinking
until (state == thinking)
fork[i] = F
send (fork) to neighbor i
end while
Informal Sketch Informally, the rules for changing priority guarantee that a
hungry process does not go “down” in the partial order, unless it eats. That is,
any changes made to the partial order do not increase the number of paths to
a hungry process.
Also, a hungry process—if it does not eat—must rise in the ordering. This
follows from the observation that a hungry process does not remain at the top
indefinitely. When such a hungry process eats, it falls to the bottom and other
hungry processes go “up”.
We can then show that a hungry process does not remain at the top indefinitely:
These two properties together give us the desired progress property for the
metric:
u.h ∧ u.m = k ; u.e ∨ (u.h ∧ u.m < k)
And this metric property (using induction) gives the need result (i.e., u.h ;
u.e ).
For a complete treatment, refer to the Unity book.
Snapshots
9.1 References
1. The seminal paper on snapshots was written in 1985 by Chandy and Lam-
port [CL85].
2. The Singhal and Shivaratri text book [SS94] contains a good description
of snapshots in Sections 5.6 and 5.7.
3. The Unity book presents snapshots in Chapter 10 (but for an informal
presentation, the previous reference is better).
( Σ p : p.e : 1 )
101
102 CHAPTER 9. SNAPSHOTS
Our task is to determine this global system state. This is also known as
“taking a snapshot”. It is important to do this without stopping the underlying
computation. That is, as this snapshot is being taken, the state can be changing!
Recall that there is no global clock in our model of computation. If there
were, the problem would be easy. We would simply require all processes to
record their local state at a particular time, say 12:00. What is the state of each
channel in such a snapshot? The messages in transit. Question: how can this
state be calculated? Answer: .
As a crude intuitive analogy, consider an army of ants, each with their own
local view of the world. They want to coordinate their behavior so as to form a
complete picture of an elephant! A single ant takes a small picture, and together
the collage has to form a coherent picture the entire elephant. The challenge
lies in the facts that (i) the ants do not have a synchronized notion of time and
(ii) the elephant may be moving!
These individual views are joined to form a snapshot of the global state. A
snapshot, then, is like a “wavey cut” through a timeline, as shown in Figure 9.2.
Notice, however, that not all cuts make sense! For example, consider a
distributed network containing bank account information. Initially, the balance
at bank node 1 is $1000, while the balance at bank node 2 is $0. A transfer of
$500 is requested from node 1 to node 2. This computation is represented in
Figure 9.3.
Now, if a snapshot is taken and the bank nodes record their local state at
the x’s, the net worth recorded by the snapshot is . Therefore,
this snapshot has failed to capture an accurate view of the global system state.
9.4. CONSISTENT CUTS 103
$0 $500
Bank 2
$500
Bank 1
$1000 $500
• A cut is consistent.
$0 $500
Bank 2
$500
Bank 1
$1000 $500
Therefore, one way to guarantee that a cut is consistent is to use the con-
9.6. UTILITY OF SNAPSHOTS 105
In other words, every process records its state at the same logical time. Question:
how is the state of each channel calculated? Answer: .
$0 $200 $700
Bank 2
$500
Bank 1
$1000 $500
Now notice the global state recorded by this consistent cut. The net worth
of the account is $1000 ($500 at node 1 and $500) in transit. But this global
state never occurred in the actual computation! The net worth of the account
was $1200 during the transfer.
Nevertheless, we consider such a cut consistent (i.e., valid). Why would
having a global state that may never have occurred be useful?
Put another way, even though the snapshot state is not guaranteed to have
occurred, what is guaranteed? Answer: .
View the collection of snapshot information as a computation (superimposed
on the original computation) that takes some time (i.e., some number of ac-
tions). It begins in state Sb and ends in a state Se . This is represented in
Figure 9.7.
What we require of the snapshot state, Ssnap , is:
106 CHAPTER 9. SNAPSHOTS
actual computation
Sb Se
actual computation
Sb Se
possible
computation
S snap
Sb pre post Se
pre post
pre post
pre post
S snap
When we do this swapping, we will preserve the order of the “pre”s with
respect to each other as in the original computation. Similarly, we will preserve
the order of “post”s.
Consider a pair of actions in the sequence that are out of order. Such a pair
has the form < apost , bpre > . First observe that these two actions must be on
different processes. There are three cases for bpre :
1. bpre is a local action. In this case, it is easy to swap this action with
apost , since the two can not affect each other.
108 CHAPTER 9. SNAPSHOTS
Termination Detection
1. send a message,
2. receive a message, or
An idle process, on the other hand, can only perform a single action: receive
a message, at which point it becomes active . Complete Figure 10.2 to indicate
these allowed state transitions.
The program for such a process is given below.
109
110 CHAPTER 10. TERMINATION DETECTION
active idle
Program Pi
var state : {active, idle}
out : { Pi ’s outgoing channels}
in : { Pi ’s incoming channels}
always (act ≡ state = active)
∧ (idl ≡ state = idle)
initially act
∧ ( ∀ c : c ∈ out : empty.c )
assign
( [] c : c ∈ out : act −→ send msg on c )
[] ( [] c : c ∈ in : act ∧ ¬empty.c −→ rcv msg on c )
[] act −→ state := idle
[] ( [] c : c ∈ in : idl ∧ ¬empty.c −→ state := active
; rcv msg on c )
Notice that the initial conditions indicate that all processes are active and
all channels are empty.
Question: When should we consider such a system to be “terminated”? You
can answer the question intuitively. You can also answer the question by cal-
culating the F P for this program. Answer: the computation has terminated
when:
1. , and
2. .
10.2 Algorithm
We begin by introducing a special “detector” process. All processes are assumed
to have a channel to the detector. See Figre 10.3.
When a process becomes idle , it sends a message to the detector. So,
when the detector has heard from all processes, it knows that all processes
10.2. ALGORITHM 111
For example, a process with two incoming channels ( d and e ) and two out-
going channels ( f and g ), as illustrated in Figure 10.4, would send a message
to the detector of the form:
< id, inputs{(d, r.d), (e, r.e)}, outputs{(f, s.f ), (g, s.g)} >
where r.c is the number of messages received on channel c and s.c is the
number of messages sent on channel c .
At the detector, the number of messages put into a channel c (i.e., sent)
are recorded as in.c , while the number of messages taken out of a channel c
(i.e., received) are recorded as out.c . The detector looks for the condition:
• ( ∀ c :: in.c = out.c )
112 CHAPTER 10. TERMINATION DETECTION
d f
e g
Notice that this modification solves the problem illustrated earlier with the
erroneous detection of termination for the system with 2 processes, p and q .
With this augmented protocol, the detector would have received a notification
from all processes (satisfying the first condition), but the value of in.c would
be while the value of out.c would be . Hence, it would not be
the case that in.c = out.c and the detector would not report termination.
10.3 Specification
We define the boolean predicate done , set by the detector, by:
That is:
( ∀ c :: s.c ≥ r.c )
But, since done is true, we know that for this particular cut we have the
property:
( ∀ c :: s.c = r.c )
Therefore it is a valid snapshot. Therefore, termination being a property of this
view of the computation means that it is a property of the current computation
as well (since termination is stable).
2
114 CHAPTER 10. TERMINATION DETECTION
Chapter 11
Garbage Collection
11.1 Reference
The presentation of this material follows chapter 16 in the Unity book [CM88].
For example, shade in the vertices in Figure 11.1 that are food.
root
115
116 CHAPTER 11. GARBAGE COLLECTION
A “mutator” process modifies the graph by adding and deleting edges. (Re-
call that the set of vertices is fixed.) The mutator makes these modifications
subject to the constraint that an edge may be added only if it points to food.
Our task is to write a “marker” process that runs concurrently with the
mutator and is responsible for marking the food. After it is done, we can be
sure that all the food has been marked. Put another way, after it is done we
know:
x is unmarked ⇒ x is garbage
Notice that this is implication and not equivalence. That is, the marker can be
conservative and mark things that are not food. Why do we allow this?
While it is too much to require the marker to have marked only food when it
is done, it also too weak to require simply that the marker have marked at least
all food when it is done. If this were the specification, an acceptable solution
would simply mark all the vertices. Clearly such a solution is not interesting
since we would like to collect as much garbage as possible (i.e., mark as few
extra, non-food, vertices as possible).
Which garbage vertices in particular is it reasonable to require the marker
not to mark? The answer is the vertices that were .
We call these vertices “manure”. We will settle for collecting manure (since this
mark and collect procedure can be repeated to collect the garbage that was
missed in the previous iteration.)
We want to recover useless cells and move them back to the free store list. We
use the marker algorithm described above, constantly running in the background
11.4. RELATIONSHIP TO TERMINATION DETECTION 117
to mark the things that are food and then everything else can be moved back
to the free store list. This repeats as long as the mutator process (i.e., the
program) is running.
Mutator Program
Program M utator
var x, y : vertices
initially ( ∀ x :: x.manure ≡ x.garbage )
always ( ∀ x :: (x.f ood ≡ R[root, x]) ∧ (x.garbage ≡ ¬x.f ood) )
assign
( [] x, y :: add(x, y) ∧ y.f ood −→ E[x, y] := true
[] del(x, y) −→ E[x, y] := false )
118 CHAPTER 11. GARBAGE COLLECTION
3. manure is garbage
As another exercise, use the properties above to fill in Figure 11.3 with
vertices and permitted edges.
manure
garbage food
safety: invariant.(over ⇒ )
1. A new action does not affect any of the old variables, and
The reason that superpositioning is useful is that the new program “inherits”
all of the properties of the old! That is, any property of the original program is
a property of the new program as well!
In the case of the marker program, we will begin with the mutator and
superimpose a new set of actions (called the “propagator”). We will then further
superimpose more actions (called the “detector”). The resulting program will
be the marker.
These actions are superimposed with the mutator, so the resulting program
still satisfies the behavior of a mutator. Does the resulting system, however,
now satisfy the desired behavior of a marker? In other words, does this solution
work for marking vertices?
The (perhaps surprising) answer is . This is because the actions of
the mutator can work to frustrate the marker, preventing it from ever marking
a particular food vertex. How can this happen?
To fix this difficulty, we have to do more than spread marks in a gossip-like
fasion.
Program P ropagator
var x, y : vertices
initially ( ∀ x :: x.m ≡ (x = root) )
∧ ( ∀ x :: x.manure ≡ x.garbage )
always ( ∀ x :: (x.f ood ≡ R[root, x]) ∧ (x.garbage ≡ ¬x.f ood) )
assign
( [] x, y :: add(x, y) ∧ y.f ood −→ E[x, y], y.m := true, true
[] del(x, y) −→ E[x, y] := false
[] x.m ∧ E[x, y] −→ y.m := true )
Make sure you can identify the actions that were superimposed on the origi-
nal mutator program to create this new version. Also make sure you can confirm
that they conform with the requirement on superimposed actions.
Is this program correct?
Put another way, there is only one way for a pair ( x , y ) to not be ok . Com-
plete Figure 11.4 to illustrate the situation in which ¬ok[x, y] .
x y
T ≡ root.m ∧ ( ∀ x, y :: ok[x, y] )
The first claim is that the propagator program satisfies the following speci-
fication:
invariant.(x.manure ⇒ ¬x.m)
stable.T
true ; T
invariant.(x.manure ⇒ ¬x.m)
( ∀ z :: ¬z.manure ∨ ¬z.m )
The initially part of the proof obligation can be discharged by examining the
initially section of the program. (You should be able to complete this using
basic predicate calculus.)
For the stable part, there are two actions that can modify z.m and so must
be considered.
• Action: add(x, y) ∧ y.f ood −→ E[x, y], y.m := true, true
{I}
add(x, y) ∧ y.f ood
{I ∧ y.f ood}
⇒
{I ∧ ¬y.manure}
E[x, y], y.m := true, true
{ ( ∀ z : z 6= y : ¬z.manure ∨ ¬z.m ) ∧ ¬y.manure}
⇒
{I}
• Action: x.m ∧ E[x, y] −→ y.m := true
{I}
x.m ∧ E[x, y]
{I ∧ ¬x.manure ∧ E[x, y]}
⇒
{I ∧ ¬y.manure}
y.m := true
{I}
This concludes the proof.
122 CHAPTER 11. GARBAGE COLLECTION
Chapter 12
Byzantine Agreement
12.1 References
This chapter presents three separate problems.
• The two-generals problem. This problem first appeared in a paper in 1978
by J.N. Gray [Gra78]. It is now a classic problem in networking and is
treated in many standard textbooks, including Garg’s [Gar02, chapter 24].
• Asynchronous consensus. The impossibility of acheiving consensus in an
asynchronous system with the possibility of a single failure is from Fischer,
Lynch, and Paterson in a 1985 paper [FLP85]. It is also presented in Garg’s
book [Gar02, chapter 25] as well as Lynch’s book “Distributed Algorithms”
[Lyn96, chapter 12].
• Synchronous consensus with Byzantine faults. This problem was solved
in [PSL80] and is presented in [Gar02, chapter 26].
123
124 CHAPTER 12. BYZANTINE AGREEMENT
Agreement. Two correct processes can not commit to different decision vari-
ables.
( ∀ i, j : ti ∧ tj : di = dj )
Validity. If all initial values are equal, correct processes must decide on that
value.
( ∃ k :: ( ∀ i :: vi = k ) ) ⇒ ( ∀ i : ti : di = vi )
true ; ( ∀ i :: ti )
Which of these parts are safety properties and which are progress?
Level 0
1 2 3 ... N Level 1
1,2 1,3 ... 1,N 2,1 2,3 ... 2,N N,1 N,2 ... N,N−1 Level 2
Each node in the tree is given a decision value, and these values bubble
up the tree, from children to parents, until the root is assigned a value. The
root’s value is the decision value for the process. Consider the N − 3 children
of some node (l, k, j) : (l, k, j, 1), (l, k, j, 2), ..., (l, k, j, N ) . When should the
decision values of these children bubble up to their parent? Answer: when they
. In this case, this process knows that Pj relayed the same
information (regarding Pk ’s message regarding Pl ’s message) to all processes.
In order for this tree to calculate the same value for all processes, every
message chain in the leaf nodes must include at least 1 non-faulty process. Thus,
in order to tolerate f faults, the leaf nodes must represent message chains of
length . That is, a consensus tree of height is required.
12.7. SYNCHRONOUS AGREEMENT WITH BYZANTINE FAULTS 129
Discrete-Event Simulation
13.1 References
1. An excellent survey of the field, written in 1990, is given in the article
“Parallel Discrete Event Simulation” by R. Fujimoto in CACM, 33(10), p.
30–53 [Fuj90].
131
132 CHAPTER 13. DISCRETE-EVENT SIMULATION
Sources generate “clients” (or “jobs”) that travel through this network along
the links. The clients obtain service at the server nodes. Servers can provide
service to at most a single client at a time. 1 If a client arrives at a server that
is already busy with another client, it waits in a queue associated with that
server. Finally, clients disappear from the system at sinks.
The movement of clients along links is considered to be instantaneous. Ser-
vice, however, does require time, in addition to any time spent waiting in the
associated queue. The time a client spends in the system, therefore, is the sum
of the times it spent being serviced and the times it spent waiting in a queue.
For example, Figure 13.2 illustrates a system in which clients must speak
with a receptionist before they are placed in one of two different queues for
service. After service, the clients may have to move to the other queue, or may
leave the system. In general, there can be several sources and sinks and servers
with various properties.
These systems can be simulated to gather the relevant statistics. In this way,
the systems can be tuned to optimize various performance measures of interest.
Discrete-event simulation is used to model many real-world situations such as:
banks with tellers and clients; hospitals with doctors, operating rooms, nurses,
and patients; and computer systems with resources and processes.
Systems are typically modeled as being “memoryless”. That is, the time
taken for one thing does not depend on how long was taken to perform previous
things. For example, the service time a customer experiences at a teller does
not depend on the service time the previous customer experienced at that teller.
Also, the time until the next customer arrives at the bank does not depend
on when the previous customer arrived. This assumption reflects a view that
customers are independent entities.
1. Remove the first event from the head of the event queue.
3. Insert any new events generated in the previous step into the event queue.
The “current time” in the algorithm moves in discrete steps (that depend on the
time stamps of dequeued events).
One subtlety in this algorithm is the way in which clients are generated at
sources. This is done by “seeding” the event queue with an initial arrival event.
Now simulating this arrival event not only involves inserting the new client in
the system (sending them to their first node in the system) but also involves
scheduling the next (future) arrival.
For example, consider the simple system given in Figure 13.2. Initially,
the event queue contains a single event: < arrival c1 , 1 > . This event is
dequeued (the “current time” is now 1) and simulated. Simulating this event
sends customer c1 to a server, where it begins service immediately. This results
in a future event being scheduled: the completion of service of c1 at this server.
Say this event is < complete c1 , 10 > . A second event is generated: The
arrival of the next customer. Say this event is < arrival c2 , 4 > . Both of these
events are inserted in the (now empty) event queue, in increasing order of time.
This loop repeats.
Notice that the single event queue is a bottleneck. Events must wait for ear-
lier events to be simulated, even if they are completely independent! We would
like to remove this bottleneck and parallelize (and distribute) this algorithm for
discrete-event simulation.
134 CHAPTER 13. DISCRETE-EVENT SIMULATION
s1
s2
not have an event from s2 . Similarly, s2 cannot process any events because its
queue is empty. Therefore, no events can be simulated and the simulation does
not make progress.
of the increment of null messages above the current time is known as the look-
ahead. In order for this algorithm to be correct, processes must have positive
(i.e., strictly greater than 0) look-ahead.
13.6 Extensions
Several refinements of this basic algorithm have been developed, published, and
implemented. Some of these are listed below.
3. Allow deadlock. Yet another approach is to not use null messages at all.
Rather, the system is allowed to deadlock! One of the standard deadlock
detection and recovery schemes is used to ensure that the computation is
continued eventually.
All of these variants have different strengths that become apparent under
different experimental conditions. In general, the performance of this style of
conservative discrete-event simulation is highly dependent on the magnitude of
the look-ahead. The magnitude of the look-ahead, in turn, is highly application-
specific.
If an event with an earlier time stamp does arrive, the process must undo part
of the simulation! This is known as roll-back.
A seminal paper on optimistic simulation appeared in 1985 ( “Virtual Time”
by Jefferson, in TOPLAS 7(3)) and introduced the algorithm known as time
warp. This name stems from the fact that the algorithm allows time to flow
backwards.
With optimistic simulation algorithms, messages can arrive with time stamps
that are earlier than the current logical time. Such a message is known as a
straggler. The main issue in these algorithms is how to deal with stragglers.
To address the problem of stragglers, a (time stamped) history of past states
is maintained. When a straggler arrives with a time stamp of t , the process
recovers from this history the last valid state before t . See Figure 13.4.
straggler
ts = t
t
last valid state
There is, however, a complication: This process may have already sent events
to other processes after t ! These events must be undone. This is accomplished
by sending an anti-event. The affect of an anti-event is to cancel the correspond-
ing event.
There are two cases for the arrival of an anti-event. It could arrive with a
time stamp greater than the current local time. In this case, the corresponding
event to be cancelled has not yet been simulated. The corresponding event can
therefore be deleted from the event queue. The second case is that the anti-
event arrives with a time stamp less than the current local time. In this case,
the anti-event is itself a straggler! The process receiving this straggler needs to
roll-back, potentially causing other anti-events to be sent...
Clearly, we must convince ourselves that this series of cascading roll-backs
can not continue indefinitely, thus preventing the simulation from advancing.
The proof of correctness is based on the idea of Global Virtual Time (GVT).
The GVT is the minimum time stamp in the system. We can show that:
• the algorithm never rolls back to before GVT, and
• the event scheduled at time GVT can be executed without causing a roll-
back.
138 CHAPTER 13. DISCRETE-EVENT SIMULATION
straggler
ts = t
t event
e1
Order of Operations
The operators used in these notes are listed here in decreasing order of binding
power.
• . (function application)
• ¬ (logical negation)
• ∗ / (arithmetic multiplication and division)
• + − (arithmetic addition and subtraction)
• < > ≤ ≥ = 6= (arithmetic comparison)
• ∧ ∨ (logical and and or)
• next unless ensures ; (temporal operators)
• ⇒ ⇐ (logical implication and explication)
• ≡ 6≡ (logical equivals and discrepance)
139
140 APPENDIX A. ORDER OF OPERATIONS
Bibliography
141
142 BIBLIOGRAPHY
[Gra78] Jim Gray. Notes on data base operating systems. In Michael J. Flynn,
Jim Gray, Anita K. Jones, Klaus Lagally, Holger Opderbeck, Gerald J.
Popek, Brian Randell, Jerome H. Saltzer, and Hans-Rüdiger Wiehle,
editors, Operating Systems, An Advanced Course, volume 60 of Lecture
Notes in Computer Science, pages 393–481. Springer, 1978.
[Lam78] Leslie Lamport. Time, clocks, and the ordering of events in a dis-
tributed system. Communications of the ACM, 21(7):558–565, July
1978.
[Lyn96] Nancy A. Lynch. Distributed Algorithms. Morgan Kaufman Publishers,
Inc., San Francisco, California, 1996.
[Mat89] Friedemann Mattern. Virtual time and global states of distributed
systems. In M. Cosnard et al., editors, Parallel and Distributed Al-
gorithms: proceedings of the International Workshop on Parallel &
Distributed Algorithms, pages 215–226. Elsevier Science Publishers B.
V., 1989.
[Mis01] Jayadev Misra. A Discipline of Multiprogramming: Programming The-
ory for Distributed Applications. Monographs in Computer Science.
Spring-Verlag, New York, New York, 2001.
[PSL80] M. Pease, R. Shostak, and L. Lamport. Reaching agreement in the
presence of faults. Journal of the ACM, 27(2):228–234, April 1980.
[SS94] Mukesh Singhal and Niranjan G. Shivaratri. Advanced Concepts in
Operating Systems. McGraw-Hill, 1994.