---------------- Readers and Writers ---------------- Problem: We have two classes of processes. Readers, which can execute concurrently Writers, which demand exclusive access We are to permit concurrency among readers, but when a writer is active, there are to be NO readers and NO OTHER writers active. integer #w range 0 .. maxint initially 0 integer #r range 0 .. maxint initially 0 binary semaphore S Reader() is Writer() is loop loop P(S) P(S) exit when #w=0 exit when #r=0 and #w=0 V(S) V(S) #r++ #w++ V(S) V(S) Do-the-read Do-the-write P(S); #r--; V(S) P(S); #w--; V(S) Explain the idea behind the code and why it works. (As usual) it doesn't work. We fix it with our magic elixir (redundant test before lock) integer #w range 0 .. maxint initially 0 integer #r range 0 .. maxint initially 0 binary semaphore S Reader() is Writer() is loop loop while #w>0 while #r>0 or #w>0 P(S) P(S) exit when #w=0 exit when #r=0 and #w=0 V(S) V(S) #r++ #w++ V(S) V(S) Do-the-read Do-the-write P(S); #r--; V(S) P(S); #w--; V(S) HOMEWORK What is the trying-part and what is the releasing-part? HOMEWORK Show that #w only takes on values 0 and 1 Observation #1. Since #w is only 0 and 1, we use the following code instead. integer #w range 0 .. 1 initially 0 integer #r range 0 .. maxint initially 0 binary semaphore S Reader() is Writer() is loop loop while #w>0 while #r>0 or #w>0 P(S) P(S) exit when #w=0 exit when #r=0 and #w=0 V(S) V(S) #r++ #w <-- 1 V(S) V(S) Do-the-read Do-the-write P(S); #r--; V(S) P(S); #w <--0 ; V(S) Observation #2. Assigning a 1 to #w is atomic at the hardware level. We don't need a semaphore to make it atomic. Also the other sections protected by P(S)/V(S) don't need to be protected from #w<--0. integer #w range 0 .. 1 initially 0 integer #r range 0 .. maxint initially 0 binary semaphore S Reader() is Writer() is loop loop while #w>0 while #r>0 or #w>0 P(S) P(S) exit when #w=0 exit when #r=0 and #w=0 V(S) V(S) #r++ #w <-- 1 V(S) V(S) Do-the-read Do-the-write P(S); #r--; V(S) #w <-- 0 Observation #3. Since we don't grab a lock to set #w zero, the trying readers can't prevent it. This means we don't need the elixir for them. Similarly we don't need the #w part of the elixir for the writers either. We do need the #r part otherwise we could prevent the decrement of #r at the bottom of the Reader code. integer #w range 0 .. 1 initially 0 integer #r range 0 .. maxint initially 0 binary semaphore S initially open Reader() is Writer() is loop loop while #r>0 P(S) P(S) exit when #w=0 exit when #r=0 and #w=0 V(S) V(S) #r++ #w <-- 1 V(S) V(S) Do-the-read Do-the-write P(S); #r--; V(S) #w <-- 0 New approach to readers / writers. Assume we have B units of a resource R, where B is at least the maximum possible numbers of readers (e.g. B = max # processes). Make each reader grab one unit of the resource and make each writer grab all B. Thus when a writer is active, no other process is. So how can we grab B units all at once? PChunk! counting semaphore S initially B Reader() is Writer() is P(S) PChunk(S,B) Do-the-read Do-the-write V(S) VChunk(S,B) If we use the FAA implementations for P/V Chunk and run the code on the NYU Ultracomputer Ultra III prototype, we get the property that "during periods of no writer activity (or attempted activity), NO critical sections are executed". I no of no other implementation with this property. The code above permits readers to easily starve writers. The following writer-priority version does not (but writers can starve readers). This is a generic way to take two algorithms and give one priority. That is, the above code for readers and writers are used as black boxes. counting semaphore S initially B integer #ww initially 0 -- number of waiting writers Reader() is Writer() is while #ww>0 FAA(#ww,+1) P(S) PChunk(S,B) Do-the-read Do-the-write V(S) VChunk(S,B) FAA(#ww,-1) HOMEWORK. Write the corresponding reader-priority readers/writers code. ---------------- Fetch-and-increment and fetch-and-decrement With the exception of P/V chunk (and hence readers/writers) all the FAA algorithms used an addend of +1 or -1. Fetch-and-increment(X) (written FAI(X)) is defined as FAA(X,+1) Fetch-and-decrement(X) (written FAD(X)) is defined as FAA(X,-1) It turns out that the hardware is easier for FAI/FAD than for general FAA. FAA looks like a store and a load data sent to and from memory FAI/FAD look like a load data comes back from memory it is a special load (memory must do something) Somewhat surprisingly readers and writers can be solved with just FAI/FAD. integer #r initially 0 integer #w initially 0 Reader is Writer is while #w > 0 while #w > 0 FAI(#R) if FAI(#w) > 0 if #w > 0 FAD(#w) FAD(#R) writer Reader while #R > 0 Do-the-read Do-the-write FAD(#R) FAD(#w) This is proved in freudenthal gottlieb asplos 1991.