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 stripes and threshold . Each lock gives mutual exclusion, and each read or write of one count is atomic. The relaxed counter type has state ; inc() adds 1, exact() returns , and approximate() may return any with . The counter starts with all local counts 0 and .
Lemma 1 (who writes what). Only a thread that holds writes (lines L2 and L7), and only a thread that holds writes (line L5). A call that reads holds (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.
Linearization points. An increment on stripe is moving if the value of after its line L2 is at least . By Lemma 1, no other thread changes 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 at each moment be plus the number of increments whose points have passed.
Lemma 2 (pending amounts). For each stripe define at each moment: if the holder of is a moving increment that has done line L2 but not line L5, then ; if it has done line L5 but not line L7, then ; otherwise . Then at each moment
Proof. By induction on the steps. At the start , all , and . By Lemma 1 only lines L2, L5 and L7 change a count, and only steps of the holder of change . Consider a call on stripe .
- Line L2 of a non-moving increment. before and after, so and both grow by 1, and does not change. The new is below because the increment is not moving, so .
- Line L2 of a moving increment. Before the step, no call on stripe is inside lines L2 to L7, so . The new value is at least , so the old value was exactly . After the step, and , the same as before. does not change, because the point is later. So the equation still holds.
- Line L5 of a moving increment. grows by , goes from to 0, and grows by 1, because this is the point. The sum grows by , the same as .
- Line L7. becomes 0, and is 0 before and after.
No other step changes , a count, or , and the bounds hold after each case.
Lemma 3 (results). (a) The value that approximate() reads at line L10 satisfies at that moment. (b) The value that exact() computes at line L15 is at that moment.
Proof. (a) by Lemma 2, and . (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 . Lemma 2 gives . The sum at line L15 is computed while all locks are held, so no count changes during it.
Theorem (linearizability). Each history of the sloppy counter is linearizable with respect to the relaxed counter type.
Proof. Take an execution and its history . Complete 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 . Order the kept calls by their points, which are distinct steps. Apply them in this order to the relaxed counter from . Each increment adds 1 at its point, which is how is defined, and each read returns a result allowed in the state at its point, by Lemma 3. So the sequential history is legal, and each call has the same result as in . If call returns before call starts, the point of is before the point of , so the order keeps the real-time order of .
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: gets number and gets number . Each call takes its locks in increasing number: an increment takes and then perhaps ; an exact read takes and then ; an approximate read takes only . 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 be the largest number of a held lock. Its holder waits, if at all, only for a lock with a number above , 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.