Value Queue “acquire” op “release” op “broadcast” or “release all” op 0/1 ? Lock Unlock No Block till value = 0; Value = 0 set value = 1 8: Classic Synchronization INT Y Wait Signal No? value — Value++ While (getValue()<0){ Problems and Deadlock If value < 0 If value <=0 Signal Add self to queue Wake up one } Condition N/A Y Wait Signal Easy to variable Put self on queue If on Support Last Modified: queue, wake up A signal all 6/14/2004 11:54:49 AM one Event 0/1 Y Wait Signal Default signal does If Value = 0, put Value = 1 self on queue Wake up all

Monitor 0/1 Y Call proc in monitor Return from proc No in monitor

-1 -2

Bounded Buffer Classical Synchronization Problems Producer/Consumer r Bounded-Buffer Problem r Finite size buffer (array) in memory shared by (also called Producer-Consumer) multiple processes/threads r Producer threads “produce” an item and place in in the buffer r Readers and Writers Problem r Consumer threads remove an item from the buffer and “consume” it r Why do we need synchronization? r Dining-Philosophers Problem m Shared data = the buffer state m Which parts of buffer are free? Which filled? r What can go wrong? m Producer doesn’t stop when no free spaces; Consumer tries to consume an empty space; Consumer tries to consume a space that is only half-filled by the producer; Two producers try to produce into same space; Two -3 consumers try to consume the same space,… -4

Monitor Solution to Bounded- Monitor Solution to Bounded- Buffer Buffer

container_t { //monitor boundedBuffer cont void consumer (){ BOOL free = TRUE; while( allBuffersEmpty()){ void producer (){ item_t item; wait(notAllEmpty) while( allBuffersFull()){ } } wait(notAllFull ) monitor boundedBuffer { } which = findFullBuffer (); conditionVariable notAllFull; consumeItem(which->item); conditionVariable notAllEmpty; which = findFreeBuffer (); which->free = TRUE; container_t buffer[FIXED_SIZE]; which->free = FALSE; numFull--; int numFull = 0; which->item = produceItem(); numFull++ signal(notAllFull); } signal(notAllEmpty ); } } //end Monitor

-5 -6

1 Semaphore Solution to Semaphore Solution to Bounded-Buffer Bounded-Buffer semaphore_t mutex; void producer (){ void consumer (){ semaphore_t full; container_t *which; container_t *which; semaphore_t empty; wait(empty); wait(full); wait ( mutex); wait ( mutex); container_t { BOOL free = TRUE; which = findFreeBuffer (); item_t item; which = findFullBuffer (); which->free = FALSE; } consumeItem(which->item); container_t which->item = produceItem(); which->free = TRUE; buffer[FIXED_SIZE]; signal (mutex); signal (mutex); void initBoundedBuffer { signal (full); signal (empty); mutex.value = 1; } } full.value = 0; empty.value = FIXED_SIZE •Can we do better? Lock held while produce/consume?

}

-7 -8

Semaphore Solution to Readers/ Readers/writers Writers (Reader Preference)

semaphore_t mutex; r Shared data area being accessed by multiple void reader (){ semaphore_t okToWrite; processes/threads int numReaders ; wait(mutex ); numReaders ++; r Reader threads look but don’t touch void init{ if (numReaders ==1) m We can allow multiple readers at a time. Why? mutex.value = 1; wait(okToWrite); //not ok to write r Writer threads touch too. okToWrite.value = 1; signal(mutex); numReaders = 0; m If a writer present, no other writers and no readers. } do reading (could pass in pointer to Why? void writer (){ read function) wait( okToWrite); r Is Producer/Consumer a subset of this? do writing (could pass wait(mutex ); m Producers and consumers are both writers in pointer to write function) numReaders --; m Producer = writer type A; Consumer = writer type B and if (numReaders == 0) no readers signal(okToWrite); signal(okToWrite ); //ok to write again } m What might be a reader? Report current num full. signal (mutex); Can we do better? Fairness? -9 } -10

Monitor solution to Semaphore Solution to Readers/ This one favors writers; readers/writers How? Can readers starve? Writers (Fair) Monitor readersWriters { void startWrite() { semaphore_t readCountMutex, incoming, next; int numReaders = 0; BOOL writeInProgress = while (numReaders || int numReaders; writeInProgress){ FALSE; BOOL writeInProgress, readInProgress;

int okToWriteQueued = 0; okToWriteQueued++; conditionVariable okToWrite; wait( okToWrite); void init{ conditionVariable okToRead; okToWriteQueued-- ; } readCountMutex.value = 1; void startRead () { writeInProgress = TRUE; incoming.value = 1; while (writeInProgress || } okToWriteQueued){ next.value = 1; wait (okToRead); void finishWrite(){ numReaders = 0; } writeInProgress = FALSE; writeInProgress = FALSE; numReaders ++; if ( okToWriteQueued){ signal( okToRead ); signal( okToWrite); readInProgress = FALSE; } } else { } signal(okToRead); void finishRead(){ } numReaders--; if (numReaders == 0){ } signal( okToWrite); } //end monitor -11 -12 }

2 Semaphore Solution to Readers/ Remember void reader (){ Writers (Fair) wait(incoming);

void writer (){ if (!readInProgress) { wait (incoming); wait (next); r Game is obtaining highest possible degree of wait(next); } concurrency and greatest ease of programming wait(readCountMutex); writeInProgress = TRUE; numReaders++; r Tension readInProgress = TRUE; signal(readCountMutex); m Simple and high granularity locks easy to program //Let someone else move on //to wait on next m Simple and high granularity locks often means low //If next on incoming is signal(incoming); //writer will block on next concurrency //If reader will come in signal(incoming); do writing r Getting more concurrency means do reading m Finer granularity locks, more locks writeInProgress = FALSE; m if (next.value == 0){ wait(readCountMutex); More complicated rules for concurrent access numReaders--; signal (next); if (numReaders == 0){ } readInProgress = FALSE; } if (next.value == 0){ signal (next); } } signal(readCountMutex); -13 -14

Semaphore Solution to Dining Dining-Philosophers Problem Philosophers Problem? //array of chopsticks, chopstick i is philosopher 0 gets chopstick 0 to the right of philosopher i philosopher 1 gets chopstick 1 void philosophersLife(int i){ …. semaphore_t while (1) { int rightChopstick, leftChopstick; philosopher N gets chopstick N chopstick[NUM_PHILOSOPHERS]; think(); //figure out which chopsticks I need Deadlock! Solution? rightChopstick = i; void init(){ leftChopstick = i -1+ NUM_PHILOSOPHERS % NUM_PHILOSOPERS; //grab chopsticks for (i=0; i< NUM_PHILOSOPHERS; i++) wait(chopstick[rightChopstick]) wait(chopstick[leftChopstick ]); chopstick[i].value = 1; eat(); } //putdown chopsticks signal(chopstick[ rightChopstick]) signal(chopstick[ leftChopstick]);

} }

-15 -16

Deadlock Fixing Dining Philosophers

r Deadlock exists in a set of r Make philosophers grab both chopsticks processes/threads when all they need atomically processes/threads in the set are waiting m Maybe pass around a token (lock) saying who for an event that can only be caused by can grab chopsticks another process in the set (which is also m Get a global lock before can lock any chopsticks waiting!). r Make a philosopher give up a chopstick r Dining Philosophers is a perfect example. r Others? Each holds one chopstick and will wait forever for the other.

-17 -18

3 Cycle in Resource Allocation Resource Allocation Graph Graph Philosopher 1 has chopstick 1 and wants chopstick 0 r Deadlock can be described through a resource allocation graph Chopstick 0 Philosopher 1 Chopstick 1 r Each node in graph represents a Philosopher process/ or a resource Cycle: P0, C3, P3, C2, P2, 2 has chopstick 2 Philosopher 0 C1, P1, C0, P0 Philosopher 2 r An edge from node P to R indicates that and wants process P had requested resource R chopstick 1 r An edge from node R to node P indicates Chopstick 3 Chopstick 2 that process P holds resource R Philosopher 0 has Philosopher 3 r If graph has cycle, deadlock may exist. If chopstick 0 and wants Philosopher 3 has chopstick 3 chopstick 3 graph has no cycle, deadlock cannot exist. and wants chopstick 2 wait(chopstick[i]) wait(chopstick[(i-1) % NUM_PHILOSOPHERS]) -19 -20

Better Semaphore Solution to No Cycle in Resource Allocation Dining Philosophers Graph Chopstick 0 Philosopher 1 Chopstick 1 void philosophersLife(int i){ Always wait for low chopstick first No cycle! while (1) { No deadlock! think(); Why better? if ( rightChopstick < leftChopstick}{ This is not the Philosopher 2 Philosopher 0 wait(chopstick[rightChopstick ]); One philosopher only possible wait(chopstick[leftChopstick]); reaches right first when outcome. P1 } else { all others reach left could get wait(chopstick[leftChopstick]); chopstick 0 Chopstick 3 wait(chopstick[rightChopstick ]); Two philsophers reach first, similarly Chopstick 2 } for same one and one will P3 could get Philosopher 3 lose; The one who wins chopstick 3 eat(); will get both chopsticks first. and finish – allowing Regardless, if ( rightChopstick < leftChopstick}{ signal( rightChopstick); everyone to finish someone will wait(chopstick[rightChopstick ]); signal( leftChopstick); eventually get both their wait(chopstick[leftChopstick]); chopsticks and } else { } No circular wait! No be able to wait(chopstick[leftChopstick]); wait(chopstick[rightChopstick ]); } deadlock!! finish -21 } -22

Conditions for Deadlock Deadlock Prevention r Deadlock can exist only if the following four r Four necessary and sufficient conditions conditions are met; for deadlock m – some resource must be held m Mutual Exclusion exclusively m Hold and Wait m Hold and Wait – some process must be holding one resource and waiting for another m No m No preemption – resources cannot be preempted m Circular Wait m Circular wait – there must exist a set of processes r Preventing mutual exclusion isn’t very (p1,p2, …pn) such that p1 is waiting for p2, p2 is waiting helpful. If we could allow resources to be for p3, … pn is waiting for p1 used concurrently then we wouldn’t need r All these held in the Dining Philosopher’s first the synchronization anyway! “solution” we proposed r Preventing the others?

-23 -24

4 Preventing Hold and Wait Preventing No Preemption r Do not allow processes to hold a resource when r Preemption (have to love those double negative ☺ ) requesting others r Allow system to take back resources once granted m Make philosophers get both chopsticks at once m Make some philosopher give back a chopstick m Window’s WaitForMultipleObjects r Disadvantage: Hard to program r Make processes ask for all resources they need at m System figures out how to take away CPU and memory the beginning without breaking programmer’s illusion m Disadvantage: May not need all resources the whole time m How do you take away access to an open file or a lock m Can release them early but must hold until used once granted?? Would need API to notify program and then code to deal with the removal of the resource at r Make processes release any held resources before arbitrary points in the code requesting more • Checkpoint and Rollback? m Hard to program!

-25 -26

Preventing Circular wait Prevention vs Avoidance r Impose an ordering on the possible resources and r Both actually prevent deadlock require that processes request them in a specific m Deadlock Prevention does so by breaking one of the four order necessary conditions r How did we prevent deadlock in dining m Deadlock Avoidance allows processes to make any request philosophers? they want (not constrained in ways so as to break one of m Numbered the chopsticks the four conditions) *as long as* they declare their m Made philosophers ask for lowest number chopstick first maximum possible resource requests at the outset r Disadvantage: r Both can deny resource requests that would not m Hard to think of all types of resources in system and actually lead to deadlock in practice number them consistently for all cooperating processes m Philosophers may never get into deadlock at all even with m I use a resource X and Y , you use resource Y and Z and no intervention W, someone else uses W, T, R – which is resource 1? m (shared files, databases, chopsticks, locks, events, …) Likelihood? How long do they think? How long eat? m For threads in the same process or closely related processes often isn’t that bad -27 -28

Deadlock avoidance Grant a resource? r Say we don’t want to write the code such r Consider a set of processes P1, P2, …Pn that it is impossible to deadlock could still which each declare the maximum resources prevent deadlock by having the system they might ever request examine each request and only grant if r When Pi actually requests a resource, the deadlock can be avoided system will grant the request only if the r Processes declare maximum resources they system could grant Pi’s maximum resource may ever request at the beginning requests with the resource currently r Then during execution, system will only available plus the resources held by all the grant a request if it can ensure that all processes Pj for j < I processes can run to completion without r May need P1 to complete then P2 all the deadlock way to Pi but Pi can complete

-29 -30

5 Banker’s Banker’s Algorithm r Decide whether to grant resource (loan or invest money, give a philosopher a chopstick, allow unsigned available[R]; process to obtain a lock, …) unsigned allocation[P][R]; r Let there be P processes and R resources; Keep unsigned maximum[P][R]; track of m Number of units of each resource available startProcess(unsigned p){ m Maximum number of units of each resource that each for (i=0; i< R; i++){ process could request maximum[p][i] = max number of resource i m Current allocation of each resource to each process that process p will need at one time; r Real bankers cannot return money to everyone at } once } m Have a reserve requirement and rely on federal gov’t to bail them out (FDIC) m Play odds on who will return money m Also bankers typically loan one processes resource to another; OS starts out owning the resources not borrowing -31 -32

Banker’s Algorithm Banker’s Algorithm

BOOL request(unsigned p, unsigned r){ BOOL canGrantSafely(unsigned p, unsigned r){ if (allocation[p][r] + 1 > maximum[p][r]){ unsigned free[R]; if (allCanFinish) { unsigned canFinish[P]; return TRUE; //p lied about its max }else { return FALSE; for (j=0; j< R; j++) free[j] = available[j]; goto lookAtAll; } for (i=0; i< P; i++) canFinish[i] = FALSE; } } if (available[p][r] == 0){ lookAtAll: for (i=0; i< P; i++){ //can’t possibly grant; none available allCanFinish = TRUE; return FALSE; if (!canFinish[i]) } allCanFinish = FALSE; couldGetAllResources = TRUE; for (j=0; j< R; j++){ if (canGrantSafely (p, r)) if (maximum[i][j] - allocation[i][j] > free[j]){ allocation[p][r]++; couldGetAllResources = FALSE; available[r]-- ; } return TRUE; } } else { if (couldGetAllResources){ return FALSE; canFinish[i] = TRUE; for (i=0; i< R; i++) free[j] += allocation[i][j]; } } } } -33 } //for all processes -34

Avoidance vs Prevention If don’t prevent deadlock?

r Typically, system does avoidance; r If don’t prevent deadlock - either deadlock Programmer does prevention prevention or deadlock avoidance)- then how will the system deal with deadlock if (when!) it occurs: r Deadlock avoidance usually results in r Two choices higher resource allocation by allowing more m Enable the system to detect deadlocks and if it does combinations of resource requests to recover proceed than deadlock prevention m Hope they never happen and rely on manual detection and recovery (“darn my process is hung again..kill process”) m Not always – depends on ratio of processes maximum resource demands to average r Dining Philosophers? m Force a philosopher to put down a chopstick = preemption resource demands m Kill a philosopher? (sounds a bit brutal) m Kill all philosophers?

-35 -36

6 Deadlock Detection Deadlock Detection Algorithm

BOOL deadlockHasOccured(unsigned p, unsigned r) r If don’t want to ever deny requests when have { resources to grant them, then deadlock may occur unsigned work[R]; unsigned canFinish[P];

//initialization for (j=0; j< R; j++) work[j] = available[j]; BOOL request(unsigned p, unsigned r){ for (i=0; i< P; i++){ numResourcesAllocated = 0; if(available[p][r] > 0){ for (j=0; j< R; j++) { allocation[p][r]++; numResourcesAllocated += allocation[i][j]; available[r]-- ; } return TRUE; if (numResourcesAllocated == 0){ canFinish [i] = TRUE; //can’t be deadlocked if no hold and } else { wait return FALSE; } else { } canFinish [i] = FALSE; //don’t know if this one is deadlocked } } } ...... -37 -38

Deadlock Detection Algorithm Running deadlock detection?

tryToFinishOne : for (i=0; i< P; i++){ finishedSomeoneThisTime = FALSE; r Unlike with deadlock avoidance algorithm have allFinished = TRUE; choice of when to run if (! canFinish[i]){ allFinished = FALSE; r If run on every resource request approaches if ( (i != p) || (work[r] > 1) ) ) { canFinish [i] = TRUE; avoidance finishedSomeoneThisTime = TRUE; m No sense of maximum resource requirement though for (j=0; j< R; j++) work[j] += allocation[i][j]; } r Deciding how often } m } How often is deadlock likely to occur? if (allFinished){ m How many processes will be affected? return FALSE; //no deadlock } else { m When CPU utilization drops below X%? (Overall or just if (! finishedSomeoneThisTime ){ for some processes? What if spin locks?) return TRUE; //deadlock for pi st canFinish[i] == FALSE m } else { What are signs that it might be good to run deadlock goto tryToFinishOne; detection algorithm? } } } //end deadlockHasOccured -39 -40

Recovery from Deadlock Recovering from deadlock r If system detects deadlock, what can it do r How many? to break the deadlock m Abort all deadlocked processes m Abort one process at a time until cycle is eliminated (If r What do people do after manual detection? one doesn’t resolve deadlock, wait till deadlock detection algorithm runs again? Specifically run again with m Kill a process (es) assumption that one of the processes is dead?) m Reboot the system r Which ones? m Lowest priority with canFinish = FALSE? m One that has been running the least amount of time (less work to redo) m Process that hasn’t been killed before? Anyway to tell?

-41 -42

7 Prevention vs Avoidance vs Real life? Detection r Spectrum of low resource utilization r Most used prevention technique is resource m Prevention gives up most chances to allocate resources ordering – reasonable for programmers to attempt m Detection always grants resource if they are available r Avoidance and Detection to expensive when requested r Most systems use manual detection and recovery r Also spectrum of runtime “overhead” m My process is hung – kill process m Prevention has very little overhead; programmer obeys m My machine is locked up – reboot rules and at runtime system does little r m Avoidance uses banker’s algorithm (keep max request for Write code that deadlocks and run it on Linux and each process and then look before leap) on Windows – what happens? m Detection algorithm basically involves building the full resource allocation graph m Avoidance and detection both O(R*P2)

-43 -44

Outtakes Gridapp

r What would it be like to do deadlock avoidance/detection for gridapp? r For avoidance: m Would have to declare it’s max usage each time through the loop for a thread or max usage would be the whole grid and get no concurrency? r For detection, that would be be cool

-45 -46

Dining Philosophers Example Dining Philosophers

void pickupChopsticks(int i) { monitor diningPhilosophers state[i] = hungry; { test[i]; enum State{thinking, hungry, eating}; if (state[i] != eating) State moods[NUM_PHILOSOPHERS]; self[i].wait(); conditionVariable self[NUM_PHILOSOPHERS]; }

void pickup(int i); void putdownChopsticks(int i) { void putdown( int i) ; state[i] = thinking; void test(int i) ; // test left and right neighbors void init() { test((i+ (NUM_PHILOSOPHERS-1 ) ) % NUM_PHILOSOPHERS); for (int i = 0; i < NUM_PHILOSOPHERS; i++) test((i+1) % NUM_PHILOSOPHERS); state[i] = thinking; } } }

-47 -48

8 Dining Philosophers Dining Philosophers

void philosophersLife(int i) { void test(int i) { while(1){ if ( (state[(I + NUM_PHILOSOPHERS -1) % think(); NUM_PHILOSOPHERS] != eating) && pickupChopticks(); (state[i] == hungry) && eat(); (state[(i + 1) % NUM_PHILOSOPHERS] != eating)) { putdownChopsicks(); state[i] = eating; } self[i].signal(); } } }

-49 -50

Semaphore Solution to Readers/ Semaphore Solution to Readers/ Writers (Writer Preference) Writers (Writer Preference) void writer (){ void reader (){ semaphore_t mutex1, mutex2; wait(mutex2); wait(writePending); semaphore_t writePending , readersBlock, writersBlock ;//☺ numWriters++; wait ( readersBlock ); ; if (numWriters ==1){ wait (mutex1); wait( readersBlock ); numReaders ++; int numReaders, numWriters; } if (numReaders == 1){ signal(mutex2); wait(writersBlock); } void init{ wait(writersBlock); signal(mutex1); mutex1.value = 1; do the writing mutex2.value = 1; signal(writersBlock); signal(readersBlock); signal(writePending); writePending.value = 1; wait (mutex2); readersBlock.value = 1; numWriters --; do reading writersBlock.value = 1; if (numWriters == 0){ signal(readersBlock); wait (mutex1); numReaders = 0; } numReaders --; numWriters = 0; signal(mutex2); if (numReaders ==0){ } signal(writersBlock); } } First writer waits in line with the readers; signal(mutex1); -51 Other writers wait with writers } -52

Other Classic Synchronization Problems r Sleepy Barber r Traffic lights for two lane road through a one lane tunnel (McNutt ch8 + 9)

-53

9