10 1 1 475 6504
10 1 1 475 6504
10 1 1 475 6504
1 Stack
1.1 Background
Figure 1 shows a history for three threads. Each time line corresponds to one thread. All the
threads work on a single stack s. The query s.top (i) expects an element i to be on top of stack
s. Note that s.top (i) does not remove the top item. The command s.push (i) pushes an element
i on top of the stack s.
Figure 1: History
1.2 Task
1. Is the history shown in figure 1 linearizable? Justify your answer.
2. Is the history shown in figure 1 sequentially consistent? Justify your answer.
1.3 Solution
1. The history is not linearizable. The call to s.push(1) must happen before s.top(1), because
s.top(1) expects an element i on top of the stack. The call to s.push(0) must not happen
before the call to s.top(1) took effect. The earliest point when s.top(1) can take effect is
at the start of its time span. This is however already too late for s.push(0) to take effect.
2. The history is sequentially consistent. An equivalent legal sequential history is given by:
(a) Thread 2 s.push(1)
(b) Thread 2 s:void
(c) Thread 3 s.top(1)
(d) Thread 3 s:1
(e) Thread 1 s.push(0)
(f) Thread 1 s:void
(g) Thread 3 s:top(0)
(h) Thread 3 s:0
1
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
2 Non-linearizable queue
2.1 Background
This task has been adapted from [2]. The AtomicInteger class is a container for an integer value.
One of its methods is boolean compareAndSet(int expect, int update). This method compares
the object’s current value to expect. If the values are equal, then it atomically replaces the
object’s value with update and returns true. Otherwise, it leaves the object’s value unchanged,
and returns false. This class also provides int get () which returns the object’s actual value.
Consider the following FIFO queue implementation. It stores its items in an array items, which,
for simplicity, we will assume has unbounded size. It has two AtomicInteger fields. head is the
index of the next slot from which to remove an item. tail is the index of the next slot in which
to place an item.
class IQueue<T> {
AtomicInteger head = new AtomicInteger(0);
AtomicInteger tail = new AtomicInteger(0);
T[] items = (T[]) new Object[Integer.MAX VALUE];
do {
slot = head.get() ;
value = items[slot ];
if (value == null) {
throw new EmptyException();
}
} while (!head.compareAndSet(slot, slot + 1));
return value;
}
}
2.2 Task
Give an example showing that this implementation is not linearizable.
2.3 Solution
The problem is that the two consecutive operations tail .compareAndSet(slot, slot + 1) and
items[ slot ] = x do not happen atomically. The problem is illustrated in the following execution.
2
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
1. Thread 1 q.enq(e1)
2. Thread 2 q.enq(e2)
3. Thread 2 q:void
4. Thread 3 q.deq()
5. Thread 3 q:EmptyException()
6. Thread 1 q:void
The history is not linearizable. In the history above the call by thread 2 precedes the
call by thread 3. This precedence relation must be preserved in any equivalent sequential
history. Furthermore such a history can not have any other dequeue operations other than the
one by thread 3. Therefore any such history would be invalid because a dequeue operation
after a enqueue operation must not throw an empty exception in the absence of other dequeue
operations.
6 feature −− Initialization
make (a value: INTEGER)
8 −− Initialize this node with ’a value ’.
3
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
do
10 left := Void
right := Void
12 value := a value
end
14
feature −− Access
16 left : BINARY SEARCH TREE
−− The left sub tree.
18 right : BINARY SEARCH TREE
−− The right sub tree.
20 value: INTEGER
−− The value.
22
feature −− Basic operations
24 insert (a new value: INTEGER)
−− Insert ’a new value’ into the tree .
26 require
tree does not have new value : not has (a new value)
28 do
if a new value < Current.value then
30 if not left = Void then
left . insert (a new value)
32 else
left := create {BINARY SEARCH TREE}.make (a new value)
34 end
else
36 if not right = Void then
right . insert (a new value)
38 else
right := create {BINARY SEARCH TREE}.make (a new value)
40 end
end
42 end
4
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
end
62 end
end
64 end
end
3.2 Task
1. Devise an execution sequence that demonstrates that the binary search tree from Listing
1 is not linearizable; provide a corresponding history and explain why this history is non-
linearizable.
2. Using the feature compare and swap, develop a linearizable version of the binary search
tree class. Provide only the changed features.
The feature compare and swap ($entity, test value , new value) sets the value of an entity
to new value if and only if the entity currently has the value test value ; the feature call
returns whether or not the test was successful. Here, the $ operator returns the address
of the entity.
3.3 Solution
3.3.1 Task 1
Consider the following execution based on an entity n of type BINARY SEARCH TREE:
There are only two possible equivalent sequential histories that preserve the order relation.
One of the histories lets the first thread insert its value first:
5
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
The other one lets the second thread insert its value first:
Both histories are illegal because the value 4 should not be reported as missing. Hence the
implementation is not linearizable.
3.3.2 Task 2
6
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
else
18 l cached sub tree := right
if not l cached sub tree = Void then
20 right . insert (a new value)
else
22 if not compare and swap ($right, l cached sub tree , create {
BINARY SEARCH TREE}.make (a new value)) then
right . insert (a new value)
24 end
end
26 end
end
The binary search tree is linearizable. When an insert operation runs in parallel to a number
of insert operations, then the tree will contain the values of all these insert operations. When
a has operation runs in parallel to a number of insert operations then the has operation might
return true or false for the values of these insert operations. When the has operation runs after
a number of insert operations then the has operation returns true for the values of these insert
operations. For all other values the has operation returns false.
We now take a look at a history, by going from left to right; we look at the operations in
the order in which they start. If multiple operations start at the same time, we look at these
operations in a random order. As we go through the history, we construct a new sequential
history, starting from an empty one.
• If the current operation is an insert operation then we add the insert operation to the end
of the sequential history.
• If the current operation is a has operation that returns true for a value from an already
added insert operation then we add the has operation to the end of the sequential history.
• If the current operation is a has operation that returns true for a value from a not yet
added insert operation then the has operation must have run in parallel to a matching insert
operation. In this case, we stall the has operation and add it after the insert operation
as soon as we add the insert operation. This preserves the order relation because the
insert operation ran in parallel to the has operation and hence we can choose the ordering
between the two in the sequential history.
• If the current operation is a has operation that returns false and there is no insert operation
that inserts this value then we add the has operation to the end of the sequential history.
• If the current operation is a has operation that returns false and there is an insert operation
that inserts this value then either this insert operation must have run in parallel to the
has operation or it must have run after the has operation.
– In case the insert operation ran in parallel to the has operation, we have to distinguish
two situations. One one hand, it could be that the insert operation has already been
added, in which case we add the has operation in front of the insert operation. This
preserves the order relation because the insert operation ran in parallel to the has
operation and hence we can choose the ordering between the two in the sequential
history. On the other hand, it could be that the insert operation has not been added,
in which case we add the has operation to the end of the sequential history.
– In case the insert operation ran after the has operation, we add the has operation to
the end of the sequential history.
7
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
Because we add operations in the order in which they started with the only exception of
operations that ran in parallel, the history produced in this way is an equivalent sequential
history that preserves the order relation. The history is legal because no has operation returns
true for a value that has not been added or false for a value that has been added. Hence, the
binary search tree implementation is linearizable.
4.2 Task
Consider this one-shot Peterson locking algorithm:
enter1 := true
2 turn := 2
if not enter2 or turn = 1 then
4 critical section
enter1 := false
6 end
How does this locking algorithm break if the compiler (or CPU) can reorder reads and writes
to independent variables? To see how, it may help to rewrite the algorithm so that intermediate
expressions are computed and stored into temporary variables, for example, turning a + 1 = b
into
tmp1 := a + 1
2 tmp2 := tmp1 = b
It may also help to review the proof of mutual exclusion given in slides for lecture 3.
4.3 Solution
Because the accesses to enter1 and enter2 are to independent locations, these can be reordered.
For the first processor (similarly for the second) the program could be rewritten as
8
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
5.2 Task
You are asked to provide a prototype of a lock-free solution, pseudo-code is sufficient. You can
use an integer for the score. Provide a routine to update the high score if the new score is better
and a routine to retrieve the current high score. If you need additional data structures, describe
them as well.
You are free to use the compare and swap-routine from task 3.2.
5.3 Solution
9
ETHZ D-INFK Concepts of Concurrent Computation – Assignments
Prof. Dr. B. Meyer, Dr. S. Nanz Spring 2013
l success
22 loop
−− Atomic retrieval of the current high score.
24 l data := data
l success := l data . score >= a score or else
26 compare and swap (data, l data, [ a name, a score])
end
28 end
end
References
[1] CAS-Based Lock-Free Algorithm for Shared Deques. 9th Euro-Par Conference on Parallel
Processing. Maged M. Michael 2003.
[2] Maurice Herlihy und Nir Shavit. The Art of Multiprocessor Programming. Morgan Kauf-
mann, 2008.
10