Algorithm Search⌘K

Proofs: Sloppy counter

These are the human proofs of Sloppy counter, at full length.

The sloppy counter is linearizable for the relaxed counter and deadlock-free full

This proof shows: correctness, linearizability, progress.

We use the pseudocode of the page, with pp stripes and threshold S≥1S \ge 1. Each lock gives mutual exclusion, and each read or write of one count is atomic. The relaxed counter type has state NN; inc() adds 1, exact() returns NN, and approximate() may return any vv with N−p(S−1)≤v≤NN - p(S - 1) \le v \le N. The counter starts with all local counts 0 and C.global=N0C.global = N_0.

Lemma 1 (who writes what). Only a thread that holds C.lock[i]C.lock[i] writes C.local[i]C.local[i] (lines L2 and L7), and only a thread that holds C.glockC.glock writes C.globalC.global (line L5). A call that reads C.local[i]C.local[i] holds C.lock[i]C.lock[i] (lines L3, L5 and L15).

Proof. These are all the accesses of the code to these fields, and each lies between the acquire and the release of the named lock. □\square

Linearization points. An increment on stripe ii is moving if the value of C.local[i]C.local[i] after its line L2 is at least SS. By Lemma 1, no other thread changes C.local[i]C.local[i] between lines L2 and L3 of this call, so the test of line L3 succeeds exactly for moving increments. The point of a non-moving increment is its line L2, the point of a moving increment is its line L5, the point of approximate() is line L10, and the point of exact() is line L15. Each point is a step of the call itself, so it lies between the invocation and the response. Let NN at each moment be N0N_0 plus the number of increments whose points have passed.

Lemma 2 (pending amounts). For each stripe ii define cic_i at each moment: if the holder of C.lock[i]C.lock[i] is a moving increment that has done line L2 but not line L5, then ci=C.local[i]−1c_i = C.local[i] - 1; if it has done line L5 but not line L7, then ci=0c_i = 0; otherwise ci=C.local[i]c_i = C.local[i]. Then at each moment

N=C.global+∑iciand0≤ci≤S−1 for each i.N = C.global + \sum_{i} c_i \quad\text{and}\quad 0 \le c_i \le S - 1 \text{ for each } i.

Proof. By induction on the steps. At the start N=N0=C.globalN = N_0 = C.global, all ci=0c_i = 0, and S−1≥0S - 1 \ge 0. By Lemma 1 only lines L2, L5 and L7 change a count, and only steps of the holder of C.lock[i]C.lock[i] change cic_i. Consider a call on stripe ii.

  • Line L2 of a non-moving increment. ci=C.local[i]c_i = C.local[i] before and after, so cic_i and NN both grow by 1, and C.globalC.global does not change. The new C.local[i]C.local[i] is below SS because the increment is not moving, so ci≤S−1c_i \le S - 1.
  • Line L2 of a moving increment. Before the step, no call on stripe ii is inside lines L2 to L7, so ci=C.local[i]≤S−1c_i = C.local[i] \le S - 1. The new value is at least SS, so the old value was exactly S−1S - 1. After the step, C.local[i]=SC.local[i] = S and ci=S−1c_i = S - 1, the same as before. NN does not change, because the point is later. So the equation still holds.
  • Line L5 of a moving increment. C.globalC.global grows by C.local[i]=SC.local[i] = S, cic_i goes from S−1S - 1 to 0, and NN grows by 1, because this is the point. The sum C.global+∑cjC.global + \sum c_j grows by S−(S−1)=1S - (S - 1) = 1, the same as NN.
  • Line L7. C.local[i]C.local[i] becomes 0, and cic_i is 0 before and after.

No other step changes C.globalC.global, a count, cic_i or NN, and the bounds 0≤ci≤S−10 \le c_i \le S - 1 hold after each case. □\square

Lemma 3 (results). (a) The value vv that approximate() reads at line L10 satisfies N−p(S−1)≤v≤NN - p(S - 1) \le v \le N at that moment. (b) The value that exact() computes at line L15 is NN at that moment.

Proof. (a) v=C.global=N−∑iciv = C.global = N - \sum_i c_i by Lemma 2, and 0≤∑ici≤p(S−1)0 \le \sum_i c_i \le p(S - 1). (b) An exact read holds every local lock and the global lock at line L15. So no increment is between lines L1 and L8 on any stripe, and every ci=C.local[i]c_i = C.local[i]. Lemma 2 gives C.global+∑iC.local[i]=NC.global + \sum_i C.local[i] = N. The sum at line L15 is computed while all locks are held, so no count changes during it. □\square

Theorem (linearizability). Each history of the sloppy counter is linearizable with respect to the relaxed counter type.

Proof. Take an execution and its history HH. Complete HH by keeping each pending call that has passed its point, with the result of Lemma 3 for a read that has passed line L10 or L15, and removing the other pending calls; a removed call has not changed NN. Order the kept calls by their points, which are distinct steps. Apply them in this order to the relaxed counter from N0N_0. Each increment adds 1 at its point, which is how NN is defined, and each read returns a result allowed in the state NN at its point, by Lemma 3. So the sequential history is legal, and each call has the same result as in HH. If call aa returns before call bb starts, the point of aa is before the point of bb, so the order keeps the real-time order of HH. □\square

Theorem (deadlock-freedom). If the locks are deadlock-free, then whenever some call is pending and the threads keep taking steps, some call returns. With starvation-free locks, each call returns.

Proof. Number the locks: C.lock[i]C.lock[i] gets number ii and C.glockC.glock gets number pp. Each call takes its locks in increasing number: an increment takes ii and then perhaps pp; an exact read takes 0,1,…,p−10, 1, \ldots, p - 1 and then pp; an approximate read takes only pp. Suppose that from some moment on no call returns. A thread that holds no lock and waits for one waits for a thread that holds it. Let mm be the largest number of a held lock. Its holder waits, if at all, only for a lock with a number above mm, which no thread holds, so with a deadlock-free lock it gets that lock. All code between acquires is a bounded number of steps, so this thread finishes its call. This contradicts the assumption. With starvation-free locks, each waiting thread gets its lock after finitely many other critical sections, each of bounded length, so each call returns. ■\blacksquare

Back to Sloppy counter