Notes on rest of Chap. 4 Before continuing with non-bakery approaches, do acknowledge that the unbounded variable issue really is only a theoretical one. ------------------------ What is the *mutual exclusion* strategy of ``Fast''? Lock (the variable Slow-lock) that all try to ``grab'' (set to selves). (This is the ``grab-service'' bakery model again, but without any test&set or RMW--which we must somehow *emulate*.) If the lock is in use, then restart. Grab the lock. (Others may also do so, based on same observation.) Wait until you're *sure* of no *more* grabbing. If you lost the lock, then start over. (Last grabber wins.) (Exit: Release the lock. (Previous losers will eventually start over.)) How do the ``make-sure'' test?? Enough: No one has *read* the lock but not yet acted on it (either restarting or grabbing the lock). Can tell by reading their ``program counters'' (PC's) or other shared variables or ``flags'' designed for this purpose. Problem: With bad enough timing, might keep seeing someone in (different times through) the danger zone. One possible fix: Double (!) the first line, as follows: If the lock is in use, then restart. If the lock is in use, then restart. Grab the lock. Wait until you're *sure* of no *more* grabbing. If you lost the lock, then start over. Only the second has a real danger zone. (See this?) During your wait, everyone eventually *stays* away from danger, which solves the bad-timing problem. (Exercise: Spell out the implementation in more detail.) Check: 1. Mutual exclusion (i.e., never 2 in crit). (Because earlier grabber loses the lock and thus retreats.) 2. No deadlock. (Someone trying & no one in crit ==> *someone* eventually in crit.) (Because *some* grabber must be last and thus succeed.) ------------------------ Now, how add a ``fast track'' for when there is ``no contest''? Grab at an ``express lock'' (vble. Fast-lock), as a ``longshot'': Grab the express lock. If the regular lock is in use, then restart. If the regular lock is in use, then restart. Grab the regular lock. If you still have the express lock, then jump to crit. Wait until ``things settle down.'' If you lost the regular lock, then start over. ``Settled'' enough: Either rem or instruction 1, 2, or 6 above. (Exercise: Spell out the implementation in more detail, and try to resolve it with Algorithm 4.7.) Check: 1. No competition ==> no looping. (``Fast'') 2. Still mutual exclusion. (Succeed on express track ==> others defer to you.) 3. Still no deadlock. (No success on express track ==> as before.) (Exercise: Make the argument airtight. *This* is where you may want to turn to the textbook's style.) ------------------------ No lockout, via Peterson's 2-process protocol (Algorithm 4.5): Text approach: Design protocol with a bias (``priority'') Modify to *alternate* the priority (via handoffs) Another point of view: ``Deference'' turn := not me (``After you!'') *wait until* pc(other) in rem *or* turn = me See why mutual exclusion? See why 1-bounded waiting? ------------------------ Paraphrase reveals a ``not all'' protocol (mutex just if n=2): try: omit := me *wait until* omit <> me *or* pc(everybody else) < try n-process mutual exclusion *via* this, 2 ways: 1. Use n=2 case to run a pairwise ``tournament.'' (Other n=2 solutions might also work here.) 2. Nest n-1 of them: try: try(n-1): omit(n-1) := me *wait until* omit(n-1) <> me *or* pc(everybody else) < try(n-1) try(n-2): omit(n-2) := me *wait until* omit(n-2) <> me *or* pc(everybody else) < try(n-2) ... try2: omit2 := me *wait until* omit2 <> me *or* pc(everybody else) < try2 try1: omit1 := me *wait until* omit1 <> me *or* pc(everybody else) < try1 Inductive consequences of original's properties: the desired exclusions no lockout Latter doable via ``time'' analysis: Assume bound of 1 on time between a processor's steps, bound of c on time for crit. (Desired result still a corollary?) Let T(k) = bound on time to get through k last ``hurdles,'' to crit. Want: Bound on T(n-1) Claim: T(k) <= 2T(k-1) + c + dn (for d large enough). Consequence: T(n-1) <= 2^{n-1} T(0) + (2^{n-1} - 1)(c + O(n)) = (2^{n-1} - 1)c + O(2^n). Proof of Claim: T(k) <= (time to get *through* hurdle k) + T(k-1). Suppose time to get through the *first* way alone (omitk <> me) is not already bounded by T(k-1) + c + dn. Then show we *do* get through the *second* way within time T(k-1) + c + dn. Why? Because each process *not* behind tryk then has time to get all the way through and *become* a ``behind'' process or change omitk, since O(n) + T(k-1) + c + O(n) < T(k-1) + c + dn. (The first O(n) bounds the time to finish any polling, and the second bounds the time to see that the situation has changed.) ------------------------ Remarks on the tournament alternative: 1. Instance of the 2-process protocol for each tree node (n-1 in all). 2. Therefore, more variables now shared for writing (e.g., pc's), although ``weakly'' (no chance of ``concurrent writes''). 3. Better balance ==> number of hurdles smaller. 4. Most pollings over fewer than n processes. ------------------------ Finally, the promised *lower* bounds: Convert to issues of good old ``programming''! Program *what*?? An ``adversary'' who decides interactively and incrementally what the bad case (i.e., execution) should be. I.e., a ``scheduler'' who indicates admissibly/``fairly'' enough ``who goes next'' and when a process wants to leave rem, based only on the assumption that the protocol satisfies its specs (e.g., mutual exclusion, progress). ------------------------ 1. RMW memory needs at least n states to avoid unbounded bypass Suppose *fewer* than n values suffice. We show then how to fairly create bypass as large as desired (but not infinite!). Setup: Drive the first process into crit, with final shared-memory value v1 (so that no other one will be able to get there). Drive processes 2, ..., n into try (sequentially), with respective final shared-memory values v2, ..., vn. There must be a repetition: 1 <= i < j <= n, vi = vj (n pigeons, fewer holes). After process j's entry, then, processes 1, ..., i see no evidence of i+1, ..., j being beyond rem. Therefore, progress now allows them to be bypassed as many times as we like. (We grant them no turns, which *would* be unfair if we did it *forever*.) ------------------------ 2. RMW memory needs at least Omega(sqrt(n)) states to avoid lockout [Lynch] Inductive plan: n=2 needs 2 n=4 needs 3 n=7 needs 4 n=11 needs 5 ... n = (k^2 - k)/2 + 1 needs k. Lemma: If you need a shared RMW memory with k values for n processes to avoid lockout, then you need one with k+1 values for n+k processes to do so. Proof: We show how to fairly create lockout if only k possible values. Reference execution: Drive the first process into crit (so that no other one will be able to get there). Drive processes 2, ..., n into try, with final shared-memory value vn. Drive process n+1 into try with a shared-memory value v(n+1) that can be made to recur infinitely often by running process n+1 alone (staying in try, because of process 1). (Infinitely many pigeons, k holes.) Drive process n+2 into try in a similar way. (Value v(n+2).) ... Drive process n+k into try in a similar way. (Value v(n+k).) There must be a repetition: n <= i < j <= n+k, vi = vj (k+1 pigeons, k holes). Claim: We can fairly lock out processes i+1, ..., j. How to do it: Drive processes 1, ..., j into the system as indicated above. To processes 1, ..., i, the situation is indistinguishable from the one where only *they* are in the system. (Fairly) drive processes 1, ..., i until the shared-memory value is v(i+1). (*Im*possibility would imply k-1 values suffice for i processes, and hence also for n processes (since n <= i).) Then drive process i+1 to a *repetition* of v(i+1), in the indicated way. In this way, process i+1 gets turns but remains invisible to processes 1, ..., i. Similarly for v(i+2), ..., vj, thus being technically ``fair'' to all, but in useless ways! Repeat forever. ------------------------ 3. R/W memory needs n shared vbles. to avoid deadlock (even if shared writing (``o.w. easy''), lockout, unbdd. contents) For each k <= n, we show the following: There is a schedule for processes 1, ..., k leading from the (unique?) ``idle state'' (all processors in rem) to a state that still looks idle to the n-k uninvolved processes, and in which each of the k involved processes ``covers'' (is about to write) a *distinct* shared variable. For k=n, this yields our lower bound. The proof is by induction on k, with the base case k=1 trivial. How go from k to k+1? Do the k version, followed by the covered writes and return to the idle state, over and over again, until get a repetition of the set of covered variables. (To get this by the pigeonhole principle, we need only that the number of variables is finite; otherwise, however, our ultimate result certainly holds.) Concoct the desired execution as follows: Go to the first instance of the repeated k-covering. Note that process k+1 could now go right through to crit, and that he would have to write something shared but not covered on the way. Stop him when he first does cover something additional. (He might have already written to something shared but covered.) Advance the first k processes to the second instance of the repeated k-covering (giving no turns to process k+1). The writes of the covered variables undo any damage by process k+1, so that does it.