300 likes | 330 Views
Learn advanced synchronization techniques beyond semaphores to manage multi-threaded resource access efficiently. Explore alternatives like MultiSemaphore, Events, Monitors, and tackle common synchronization problems.
E N D
Tutorial 5 Even More Synchronization! presented by: Antonio Maiorano Paul Di Marco
Quick Recap • When multiple threads share resources, we need a way to synchronize access to these resources • Recall the coffee example with pourer and drinker • Solution: Use semaphores!
Alternatives to Semaphores • Semaphores are great, but they can be difficult to use when solving certain problems • We will look at alternatives to semaphores • Note: These alternatives are more convenient, though semaphores can still be used to solve these problems
Problem 1 • 2 drummers (threads) each with his/her own drum set • Only 2 drumsticks: drumStick1 and drumStick2 (shared resources) • Both drummers want to play, and to play, a drummer needs two drumsticks
Drummer 1: drumStick1.Wait(); drumStick2.Wait(); PlayDrums(); drumStick2.Signal(); drumStick1.Signal(); Drummer 2: drumStick1.Wait(); drumStick2.Wait(); PlayDrums(); drumStick2.Signal(); drumStick1.Signal(); One solution… Semaphore drumStick1 = new Semaphore(1); Semaphore drumStick2 = new Semaphore(2); This works, but…
Drummer 1: drumStick1.Wait(); drumStick2.Wait(); PlayDrums(); drumStick2.Signal(); drumStick1.Signal(); Drummer 2: drumStick2.Wait(); drumStick1.Wait(); PlayDrums(); drumStick1.Signal(); drumStick2.Signal(); But what if… • Potential deadlock! • Problem: can’t rely on programmers to obtain semaphores in the same order
Alternative: AND Synchronization • Two functions: • WaitMulti(S1, S2, …,Sn) : Blocks calling thread until all semaphores are available • SignalMulti(S1, S2, …, Sn) : Releases all semaphores • Easy to implement • See Java code: MultiSemTest.java
Drummer 1: drumSticks.WaitMulti(); PlayDrums(); drumSticks.SignalMulti(); Drummer 2: drumSticks.WaitMulti(); PlayDrums(); drumSticks.SignalMulti(); Drummers… one more time! Semaphore drumStick1 = new Semaphore(1); Semaphore drumStick2 = new Semaphore(2); MultiSemaphore drumSticks; drumSticks = new MultiSemaphore(drumStick1, drumStick2); Problem solved!
Problem 2 • Global keyboard state object : Know when keys are pressed • Threads: ask keyboard state object when a key has been pressed • Threads may start at any time
Operating System thread: ... if (keyPressed = ‘A’) keyA.Signal(); ... User thread: ... // Wait for ‘A’ key to be // pressed before continuing keyA.Wait(); ... One solution… Semaphore keyA = new Semaphore(0); // Initially not pressed This works sometimes…
What’s the problem? • Only one thread will react upon key press • If a thread starts after the key has been pressed, the Wait() call will return immediately • The semaphore is passive: it does not take timing into account • We need an active semaphore…
Alternative: Events • Events are active semaphores with its functions defined as: • Wait() : Block until event occurs; calling thread is added to the event’s waiting queue. • Signal() : Unblock all threads on event’s waiting queue; remove them from the queue. • See Java code: EventTest.java
One more time… Only diff! Event keyA = new Semaphore(0); // Initially not pressed Operating System thread: ... if (keyPressed = ‘A’) keyA.Signal(); ... User thread: ... // Wait for ‘A’ key to be // pressed before continuing keyA.Wait(); ...
Monitors • A monitor is an Abstract Data Type – like a class • The key importance of a monitor is that only one function can be accessed at a time • An important property of ADTs for use with monitors is separation of interface and implementation
Interface / Implementation Have a public interface to the class while maintaining the implementation private: • Prevents use of internal structures • Allows the implementation to change while keeping the same interface • Allows for control of how the object is used • Enforced interface allows for proving properties about its behaviour
Monitor Analogy • A monitor can be thought of as a class where each function is a critical section • The monitor thus would have a mutex as a private data member, and • Each function would thus call mutex.Wait() before executing, and mutex.Signal() before exiting.
In Java… • A monitor can be implemented as we just described, or • As a class where all of the methods are “synchronized”, where Java does the work for you
Simple Monitor monitor SharedBalance { private int balance; public SharedBalance(int amt) {balance = amt;} public credit(int amt) {balance += amt;} public debit(int amt) {balance –= amt;} }
Readers – Writers Problem • We have many readers and many writers • Readers want to read a resource • Writers want to write to a resource • Rules: • Multiple readers are allowed • One writer can write at a time • No readers can read when a writer is writing
Building Our Solution • Before the reader or writer reads from the resource, it calls the its “start” method • The “start” methods ensure that the “coast is clear”; that it is fine to continue to read from/write to the resource • The “finish” methods allow the next thread to read or write
Our Monitor • We want to solve the problem with a monitor, so it will need to keep track of: • int numReaders = 0: number of readers currently reading • int numWriters = 0: number of writers currently writing / waiting to write • boolean busy = false: is a writer busy?
reader() { while (true){ // … mon.startRead(); // read the resource mon.finishRead(); // … } } writer() { while (true) { // … mon.startWrite(); // write the resource mon.finishWrite(); // … } } The Readers and Writers
// reader methods startRead() { while (numWriters != 0); ++numReaders; } finishRead() { --numReaders; } // writer methods startWrite() { ++numWriters; while (busy || numReaders > 0); busy = true; } finishWrite() { --numWriters; busy = false; } Our Solution
The Problem With Our Solution • While a reader/writer is executing it’s “start” in the monitor, no other processes are allowed in!!! • We have to look further for our solution… • Condition variables are used when: • a thread running in a monitor • encounters a condition that is not satisfied, • which can only be satisfied by another thread
Condition Variables This is exactly what we need! • Condition Variables are an extension of Events to the concept of monitors • It temporarily blocks current thread, • Handing over “ownership” of the monitor to another thread – to do some work that will make the condition true • The original thread regains “ownership” of monitor later on…
Regaining the Monitor What happens on a call to signal() depends on the semantics used • Hoare’s semantics: • Signaling thread (S) is immediately interrupted and monitor is given back to a blocked thread (B) • Thread S resumes (in the monitor) when B finishes using the monitor
And Another Way… • Hansen’s sematics are not immediate: • Signal is “saved” • A blocked thread will wake up only when the signaling thread finishes with the monitor * The blocked thread must always verify the condition again upon waking up, since some time has elapsed since the signal() was called • This results in less context switches, increasing system performance
How Do We Use Them? • Condition variables have three methods: • wait(): block on a variable, give up monitor • signal(): wake up a thread blocked on this • queue(): ask if are there any threads blocked on this variable - to be used as part of the condition • In Java, they correspond to the usage, within “synchronized” objects, of wait() and signal() – although there is no queue()
Return to Readers – Writers • Now that we know about condition variables, we can solve this problem easily • We now have the following variables: • int numReaders = 0 (same as before) • boolean busy = false (same as before) • Condition Variables okToRd, okToWr • Solution will use Hoare’s semantics
// reader methods startRead() { while (busy || okToWr.queue()) okToRead.wait(); ++numReaders; } finishRead() { --numReaders; if (numReaders == 0) okToWr.signal() } // writer methods startWrite() { ++numWriters; while (numReaders != 0 || busy) okToWr.wait(); busy = true; } finishWrite() { busy = false; if (okToWr.queue()) okToWr.signal(); else okToRead.signal(); } The Final Solution