Spin locks and contention

Prev: University of consensus Next: Monitors and blocking synchronization

Exercises

Exercise 7.1. The following shows an alternative implementation of CLHLock in which a thread reuses its own node instead of its predecessor node. Explain how this implementation can go wrong, and how the MCS lock avoids the problem even though it reuses thread-local nodes.

public class BadCLHLock implements Lock {
  AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());
  ThreadLocal<QNode> myNode = new ThreadLocal<QNode>() {
    protected QNode initialValue() {
      return new QNode();
    }
  };
 
  public void lock() {
    QNode qnode = myNode.get();
    qnode.locked = true;
    QNode pred = tail.getAndSet(qnode);
    while (pred.locked) {}
  }
 
  public void unlock() {
    myNode.get().locked = false;
  }
 
  static class QNode {
    volatile boolean locked = false;
  }
}

Exercise 7.2. Imagine threads, each of which executes method foo() followed by method bar(). Suppose we want to make sure that no thread starts bar() until all threads have finished foo(). For this kind of synchronization, we place a barrier between foo() and bar().

First barrier implementation: We have a counter protected by a test-and-test-and-set lock. Each thread locks the counter, increments it, releases the lock, and spins, rereading the counter until it reaches .

Second barrier implementation: We have an -element Boolean array b[0..n - 1], all initially false. Thread 0 sets b[0] to true. Every thread i, for , spins until b[i - 1] is true, sets b[i] to true, and then waits until b[n - 1] is true, after which it proceeds to leave the barrier.

Compare, in 10 lines, the behavior of these two implementations on a bus-based cache-coherent architecture. Explain which approach you expect will perform better under low load and high load.


Exercise 7.3. Show how to eliminate the separate cluster-local field that records whether the lock is passed locally by recording this information directly in each QNode, as described in Section 7.7.3.


Exercise 7.4. Prove that the CompositeFastPathLock implementation guarantees mutual exclusion, but is not starvation-free.


Exercise 7.5. Design an isLocked() method that tests whether any thread is holding a lock, but does not acquire the lock. Give implementations for:

  • a test-and-set spin lock,
  • the CLH queue lock,
  • the MCS queue lock.

Exercise 7.6. Hard: where does the space complexity lower bound proof for deadlock-free mutual exclusion of Chapter 2 break when locks are allowed to use read-modify-write operations?

Prev: University of consensus Next: Monitors and blocking synchronization