Synchronization --------------- Issues: race condition, critical section, mutual exclusion, progress, starvation deadlock = each thread is blocked waiting for an event that can be caused only by another blocked thread livelock = a thread is stuck forever attempting some action that always fails Many ways to solve synchronization problems! 1. Strict alternation Simple approach, might be good enough for your application. In general, we want to be able to respond to what processes actually need. 2. Turn off interrupts User process should not do this. What if the user forgets to turn interrupts back on? The system grinds to a halt. 3. Peterson's solution maintain two variables: "ready" array to see who wants enter their critical section turn - identifies who is currently in its own critical section Don't enter critical section until you check both variables. After critical section, you are no longer "ready". Disadvantage: array operations could take a long time 4. Semaphore an integer two kinds: binary (called a "mutex") and counting Before critical section, call acquire() effect is to decrement. If 0, go to sleep until nonzero. Behind the scenes, a wait queue on the semaphore. After critical section, call release() effect is to increment. Semaphore coding can be error prone. - if you confuse acquire with release, etc. - if you modify the wrong semaphore, or in the wrong order 5. Monitor a high level language construct, like a class exists in some newer programming languages Leaves the mutual exclusion implementation up to the compiler By itself, ensures only mutual exclusion How? Only 1 thread is allowed to execute monitor code at a time! To also handle deadlock & starvation, use conditional variables too defined as a local variable inside monitor 2 ways to use: cv.wait() and cv.signal() reminiscent of semaphore For example, there could be a condition variable associated with each thread. When you are unable to grab a resource, you call wait() to put yourself to sleep. When you release a resource, you know which process(es) to signal. Signalling an already awake process is harmless. signal() subtleties: 3 possible interps based on implementation - when you signal, you immediately leave monitor, like a return :) - or you can block yourself while anybody hungry can continue - or you continue, and as you leave the hungry process can continue 6. Synchronization in Java (May remind you of process states) A class containing shared data should have "synchronized" methods for modifying it. These methods are called by the various running threads. Inside these methods, we call wait(), notify() or notifyAll() as appropriate. When a method is synchronized, you must have the lock on the object in order to enter the method. If not, you are put to sleep in the "entry set." While in a synchronized method, if you can't logically continue, you can call wait() to enter the "wait set" and sleep. notify(): The JVM will pick a random thread in the wait set, and place it in the entry set, to make it eligible to continue. notifyAll() will do this to all threads sleeping in the wait set.