Simon Tan CS 160 - Spring 2007 Lecture Notes for January 31, 2007 [NOTES BEGIN] The NACHOS reader is now available for purchase at the copy shop. ---++ Semaphore Examples ---+++ Producers & Consumers Producers create information, putting it into buffers for consumers: ---- ---- ---- ---- P --> | | --> | | --> | | --> | | [Linked list of empty buffers] | ---- ---- ---- ---- | ---- ---- ---- ---- C --> | | --> | | --> | | --> | | [Linked list of full buffers] ---- ---- ---- ---- When a Consumer finishes, puts it on the Producer's list. When a Producer finishes, puts it on the Consumer's list. Initialization: * Put all buffers in a pool of empties * Initialize semaphores: * empties = N * fulls = 0 * mutex = 1 // marks critical section Producer Process: P(empties); // Decrement number of empty buffers P(mutex); // Lock critical section get empty buffer from pool of empties; V(mutex); // Unlock critical section produce data in buffer; P(mutex); // Lock add full buffer to pool of fulls; V(mutex); // Unlock V(fulls); // Increment number of full buffers Consumer Process: P(fulls); // Decrement number of full buffers P(mutex); // Lock get full buffer from pool of fulls; V(mutex); // Unlock consume data in buffer; P(mutex); // Lock add empty buffer to pool of empties; V(mutex); // Unlock V(empties); // Increment count of empty buffers Note that the consumer process is nearly a mirror image of the producer process. Could have separate semaphores; the current mutex blocks both when locked. Order of Ps important - prevents deadlock. Order of Vs not important - does not matter if V(mutex) comes before V(empties). ---+++ Readers & Writers This example includes a binary mutex as well as mutual exculsion and scheduling.This is a VERY IMPORTANT EXAMPLE. Imagine a shared database with readers and writers. Writers are considered as readers, too, because they (read-modify-write). Writers only write when there are no readers or writers. Shared variables: * AR = active readers * WR = waiting readers * AW = active writers (this is either 0 or 1) * WW = waiting writers * Use mutex to lock access to variables Note that AR and AW cannot both be non-zero (only one active thing at a time). Every writer is a 'bookmark' - everyone behind him waits. Initialization: * OKToRead = 0 * OKToWrite = 0 * Mutex = 1 * AR = WR = AW = WW = 0 Reader Process: P(Mutex); if ((AW+WW) == 0) // Only if no writers in the system can we go { V(OKToRead); AR = AR+1; } else WR = WR+1; V(Mutex); P(OKToRead); // Testing to see allow to read -- read the necessary data; P(Mutex); // Lock critical section AR = AR-1; // We finished reading if (AR==0 && WW>0) // Let the writers go ahead { V(OKToWrite); AW = AW+1; WW = WW-1; } V(Mutex); Writer Process: P(Mutex); if ((AW+AR+WW) == 0) //(do we need WW? NO, not really. Waiting writers should have been taken care of) { V(OKToWrite); AW = AW+1; } else WW = WW+1; V(Mutex); P(OKToWrite); // Test semaphore -- write the necessary data; P(Mutex); // Lock critical section AW = AW-1; // We finished writing if (WW>0) // If more waiting writers, we all go { V(OKToWrite); AW = AW+1; WW = WW-1; } else while (WR>0) // Let all the readers go { V(OKToRead); AR = AR+1; WR = WR-1; } V(Mutex); We always give ourselves permission before we do something. Note that Writers get priority in this scheme, because readers want the most up-to-date information. The first writer to P(mutex) is not guaranteed to be the first writer to access data. The mutex only guarantees one process is in the critical section, not the order. Order of writers depends on the semaphore. ---+++ Dining Philosophers Philosophers live a simple life: Think | ~ Eat | ------^ Imagine that a group of philosophers (N+1, or 5) are out to eat at an Italian restaurant. They sit in a circle around a table, and have only one fork between each two of them: | -- _ _ | | -- | The obvious solutions (everyone picks up his right fork, etc.) do not work and lead to deadlock. Working solution: (pre-fab solution) * N+1 philosophers * Semphore mutex for mutual exclusion and access to variables, initialized to 1 * Array of variables H[i] representing state of a philosopher: Not hungry, hungry, or eating * Array of variables prisem[i] representing private semaphore for each philosopher * Separate procedure named "test" that a philosopher uses to see if its possible to eat test(me): // Runs when philosopher is hungry // If guys to the left or right are not eating and I'm hungry, I set myself to be eating and ... H(me):=eating V(prisem(me)) // give myself permission to eat loop: P(mutex) // Lock mutex for editing of H array H(me) := hungry; // This philosopher's hungry test(me) // Check if ready to eat, lock state variables V(mutex) // Unlock P(prisem(me)) // Test eat P(mutex) // Lock critical section after eating H(me):=not hungry // Not hungry any more test(left) // Make sure he's not waiting for you test(right) // Make sure he's not waiting for you V(mutex) // Unlock critical section ===+++ Threads What is a thread? * Sometimes called a lightweight process * A stream of execution with its own program counters, stack, etc. (state) but sharing open files, data arrays, etc. * Process consists of multiple threads sharing state Why have them? * Want to share state * Switching between threads is low overhead, as opposed to switching processes * Threads can generate child threads, keeps branching * Sharing state leads to efficiencies, but less security * Convenient way of programming * Good for multiprocessors, parallel program (which is difficult, rare) * Sometimes is good ---++ Semaphore Implementation We haven't built semaphores into hardware because it is difficult/complicated. They manage queues, do too much (scheduling), etc. Must be effectively atomic. Need simple way to get mutual exclusion to get Ps/Vs. Don't want to do it the dirty way (disabling interrupts or traps). Users can't disable interrupts anyway - it results in a system call for every P/V. One example: * Assume we have two atomic operations ** Increment in memory and load the value ** Decrement value in memory For busy waiting: * waiting = A = 0 loop: if A >= 1, then decrement A, go to loop [Critical section here] Decrement memory location A There's a bug: Everyone can stay stuck in the loop incrementing, and A will never be 0. Significant probability of happening. Will explore further examples next time. [NOTES END]