Yakov Y. Ivanov Lecture Notes on 2/2/2005 Last time we looked at 4 solutions to "too much milk problem." Lets look at a more powerful synchronization mechanism. Requirements for a mutual exclusion mechanism are: . Make sure that there is only one process in the critical section at given time. (which is going out and shopping in the case of too much milk problem) . Progress requirement. That is, if there is several requests at once, we must allow one to proceed. Two collegues always insisted "you first." . Allow processes to fail (go on vacation) only outside of critical section. Problem with our solution is that the roommate was not allowed to leave town wile he is shopping. . No idefinite postponement. That is, no process waits indefinitly. . Efficient solution. So no process uses resources while its waiting. Nobody is in the loop, testing a variable. The better solution is simpler. Simpler means that one is able to solve harder problems with it. And also there are principles on how we will use the solution. We want to modify shared state. The only correct way to manipulate shared state is to lock it before modifications are made, and to unlock after we manipulated it. But if its locked, we wait until the shared state is unlocked. We also assuming that there will be no failures in the critical section, and that it will be short and small. Definitions from berfore: Process is said to be running, if its getting CPU cycles. If the process is blocked, its waiting for something. (IO) Questions? How do you lock shared data? Answer: Circular definition. Definition of Semaphores: A synchronization variable that takes non-negative integer values. invented by Edsgar Dijkstra in the mid-1960's. There are two operations defined on semaphores: You can P (proberen - test in Dutch) the semaphore. This is an atomic operation. This operations attempts to decrement a semaphore. If the semaphore is positive, the operation succeeds, if the semaphore is zero, the process that P'ed the semaphore is blocked, until other process increments this semaphore. V (verhogen -- to increment in Dutch) operation is a converse of P. Its an atomic operation. If there is at least one process waiting on the semaphore, one process is released, otherwise the semaphore is incremented. So if the semaphore gets down to 0 and you're trying to P it, you get blocked. Digression into French vs. English speakers. Boos in English. Question: how do you use semaphores? Answer: Semaphores are free, you use them for whatever you need. They are free, just create them. Semaphores are not implemented in hardware, but there is a synchronization instruction. Its very hard to do semaphores in hardware, i.e. to provide a state machine. So semaphores are implemented in software, using the instructions provided by the hardware for synchronization. Now, solutions for too much milk problem with semaphores. Processes A and B do this: 1.) P (OKToBuyMilk) 2.) if (NoMilk) BuyMilk 3.) V (OKToBuyMilk) Initially, OKToBuyMilk = 1. Question: Does the initial value determines how many processes are allowed to take the resource? Answer: Initial value determines the initial value. It's however you use it. We were never quite sure whether those solutions without semaphores ever worked correctly. Specifically, we can define binary semaphores: those semaphores that are allowed to have only value 0 or value 1. Counting semaphores are those that are not binary. Semaphores provide mutual exclusion. Correctness is easy to determine (but problems get harder) Semaphore solutions work for arbitrary number of processes, rather than just two. one can have many critical sections as one wants. Those critical sections can lock same or different semaphores. Use one semaphore for one resource or multiple semaphores for multiple resources. Semaphores eliminate busy wait. But they have some minor businy waiting implementation. Use semaphores for two things: 1.) Mutual exclusion during critical section. Always use binary semaphores here. 2.) Scheduling in the sense of sequencing, allowing processes to wait for something to happen. Examples of semaphore use: Producer - Consumer problem. Reader - Writer, and Dining philosophers. Producer - Consumer. One process creating information, second process is using information. One reads the date from the disk the other does something with the data, for example. But one want those processes to work asynchronously, so one has some number of buffers. There are two buffer pools: One buffer pool of empty buffers. And a buffer pool of full buffers. Producer creates stuff and puts it into full buffers pool, consumer takes full buffer and empties whatever was there, information, coal-hoppers. So we want to synchronize the prod. and consumer. We don't want to empty info from empty buffers and put info into full buffers. Definition of "what's correct:" 1.) Consumer must wait for the producer to fill the buffer. (Scheduling) 2.) Produces must wait for the consumer to empty the buffer. (Scheduling again) 3.) We want only one process to deal with any buffer pool at a time. We will use a separate semaphore for each of these constraints. Initialize the system, all buffers are starting as empty. There is a counting semaphore called empties, which will start at N. Counting semaphre called fulls, which will start at Zero. We will have a binary semaphore called mutex for the buffer pool. Code that "should do that": Producer: P (empties) P (mutex) // get an empty buffer from the pool of empties. V (mutex) // add the full buffer to the pool of fulls. V (mutex) V (fulls) Consumer: P (fulls) P (mutex) // get full buffer from the buffer of fulls. V (mutex) // Consume the data in the buffer P (mutex) // Add empty buffer into the pool of empties. V (mutex) P (empties) One could have two mutexes for pool of empties and for the pool of fulls instead of one mutex for both. Does that work for N produces and M consumers? Yes, this solution works for any number of producers and consumers. Producer-Consumer problem is everywhere. In the supermarket. The problem Illustrates the two uses of semaphores: mutexing and sequencing. Another famuos problem: Readers-Writers problem. Classic example of the database. Bank of America's checking accounts. You have readers and writers. You're at the bank, checking how much of the balance you've got. We will have some conditions: 1.) Its safe for any number of readers to read the database simultaneously. 2.) If anyone is writing the data, then nobody else can be using it. Because the shared state is changing. Use semaphores to enforce this restriction. Impose some sequencing constraints: 1.) A Writer can only proceed if there is nobody currently reading or writing. OKToWrite. 2.) Reader can onlty read if there is no one writing and no one waiting to write. OkToRead. Some state variables. We'll use a mutex to lock the state variables: AR - Active Reader WR - Waiting Reader AW - Active Writer WW - Waiting Writer Can't help multiple writers. And writers get preference. Reader: P (mutex) if ((AW + WW) == 0) { V (OKToRead) AR++ } else { WR++ } V (mutex) P (OKToRead) // read necessary stuff P (mutex) AR++ if (AR == 0 && WW > 0) { V (OKToWrite) AW++ WW-- } V (mutex) Writer: P (mutex) if ((AR + AR + WW) == 0) { V (OKToWrite) AW++ } else { WW++ } V (mutex) P (OKToWrite) // write necessary data. P (mutex) AW-- if (WW > 0) { V (OKToWrite) AW++ WW-- } else { while (WR > 0) { V (OKToRead) AR++ WR-- } } V (mutex) Dining philosophers Problem. Digression: Compares funding of Philosophy dept. versus Computer Science department. Philosophers have very simple lifestyle: they either eat or they philoss. (high end restaurants...) Chinese vs. Italian restaurants. One fork between each plate. Can't say "All even philosophers, pick up your forks." Sol1. Obvious solution. They pick up the fork and wait for the other. Sol2. We pick up the left fork ... the right fork.... Mutex. Array 0 to N, which represents the state of each philosopher, which is either Not hugry, hungry, or eating. Array of semaphors - prisem, private semaphores, one for each philosopher. Procedure: test(number of the philosopher). Look to the left. If the guy on the left is not eating and I'm hungry, and the guy on my right is not eating, we set my state to eating and do V on prisem, which gives me permission to eat. A Philosopher thinks, then sets the state to hungry, calls test, does V on mutex, now he attempts to do a V on prisem, if he's been given permission to eat he eats, and otherwise he's blocked until forks become available. He eats, locks shared state, P on mutex, sets his own state to not hungry, calls test for left and right neighbors. He does V on mutex. Unlocks the shared state. If you're blocked on private semaphore, you wait on your neighbors to test, that is to put down the fork. Go over the procedure again. But this solution will permit a philosopher to starve between the two hungry philosophers. Unbounded delay. Assumption: philosophers duty cycle is more than 50%. Threads -------- Paper on threads. #5. Thread is a lightweight process. Its very cheap to switch threads. Thread shares its code, data, and other operating system resources. Have multiple execution of the same code at the same time. Task - all the threads together, Heavyweight, ordinary process. Advantages of threads: 1.) Switching cost is less. Because all we need to do is to reload a bunch of registers. A user can reload registers. Switching processes is an OS's function. Protection issues. If the user does the switching, then if one thread does the switching, all threads are blocked. Threads can create child threads of its own. Sharing is cheap. But there is no protection amoung them. If one thread crashes, than all threads crash. Why use threads? A Model for programming. Some algorithms are more easily done in parallel. In multiprocessor system, you get more throughput as each processor runs each thread. Much less overhead than separate processes. Q: How do processors share state? A: They share memory. OS sets that up. Q: Multiprocessing vs. Hyperthreading. Digression into switching to Law schools. Process synchronization with condition variables. -------------------------------------------------- Processes and Threads cooperate using Wait () and Signal () along with condition vars. if you do X.wait () -- that means that the process invoking the wait() will wait until some other process will invoke X.signal (). So you're waiting on X, and somebody will release you with X.signal () command. X.signal () -- resumes exactly one suspended process. If there are none, then it has ne effect. These are used specifically in monitors. The critical region, which only one process can execute at a given time. Digresson: Quality of jokes. You can write an OS using either P&V or monitors. Monitors are synch. mechanisms provide: 1.) Access to shared data. 2.) operations on the data 3.) Synchronization and scheduling We will wait on a condition. The process waits on the condition. Signal wakes up one process. Mesa Semantics after a language called Mesa. Process gets on the ready list.