Click here to load reader

Operating Systems Lecture Notes Synchronization Matthew Dailey Some material © Silberschatz, Galvin, and Gagne, 2002

  • View

  • Download

Embed Size (px)

Text of Operating Systems Lecture Notes Synchronization Matthew Dailey Some material © Silberschatz,...

  • Operating Systems Lecture NotesMatthew DaileySome material Silberschatz, Galvin, and Gagne, 2002Synchronization

  • SynchronizationWHAT:To synchronize is to make things happen at the same time.HOW:By making one process wait for another.This guarantees that certain points in each process occur at the same time.WHY: Ensure consistency of shared data (prevent race conditions) Make processes wait for resources to become availableThis is arguably the most important topic in the course!Readings: Silberschatz et al., chapter 7

  • Why do we need to synchronize?EXAMPLE: Bounded buffer problem with shared memoryProducer:

    while ( 1 ) {/* produce item -> nextProduced */while ( counter == BUFFER_SIZE ); /* do nothing */buffer[in] = nextProduced;in = ( in + 1 ) % BUFFER_SIZE;counter++;}Consumer:

    While ( 1 ) {while ( counter == 0 ); /* do nothing */nextConsumed = buffer[out];out = ( out + 1 ) % BUFFER_SIZE;counter--;/* consume item -> nextConsumed */

  • Example continuedProducer and consumer code is correct if they do not run concurrently.But with concurrency, the answer could be incorrect!

    Suppose the C statement counter++ is implemented in assembly asregister1 = counterregister1 = register1 + 1counter = register1

    Suppose the C statement counter-- is implemented in assembly asregister2 = counterregister2 = register2 - 1counter = register2

  • Example continued: interleaved executionIf the producer and consumer run concurrently, execution can be interleaved:

    T0:producer executesregister1 = counter(reg1 = 5)T1:producer executesregister1 = register1 + 1(reg1 = 6)T2:consumer executesregister2 = counter(reg2 = 5)T3:consumer executesregister2 = register2 - 1(reg2 = 4)T4:producer executescounter = register1(counter = 6)T5:consumer executescounter = register2(counter = 4)

    But the correct answer at the end should be counter = 5!!

  • Race ConditionsIf we swapped T4 and T5 in the previous interleaving:Result would be counter = 6The outcome depends on the order of execution!

    A race condition is when the outcome depends on the particular order in which instructions execute.

    To guard against race conditions we must synchronize the processes/threadsThis is not a toy example! It happens all the time in complex multitasking systems.

  • Critical Section ProblemSuppose we have n processes, P0, P1, , Pn-1.Each process has a special segment of codeCalled the critical sectionThat updates shared dataGOAL: design a protocol such that when one process is executing in its critical section, no other process is allowed into its critical section.Another way of saying it:Critical section execution must be mutually exclusive in time.

  • Lets look at the code againProducer:

    while ( 1 ) {/* produce item, then put in nextProduced */... ... ...while ( counter == BUFFER_SIZE ); /* do nothing */buffer[in] = nextProduced;in = ( in + 1 ) % BUFFER_SIZE;counter++;}Consumer:

    while ( 1 ) {while ( counter == 0 ); /* do nothing */nextConsumed = buffer[out];out = ( out + 1 ) % BUFFER_SIZE;counter--;/* consume item in nextConsumed */... ... ...

    }Where are the critical sections?We have shown that counter needs to be protected in a critical section.With careful analysis, you will see that buffer does not need protection.

  • Lets look at the code againProducer:

    while ( 1 ) {/* produce item, then put in nextProduced */... ... ...while ( counter == BUFFER_SIZE ); /* do nothing */buffer[in] = nextProduced;in = ( in + 1 ) % BUFFER_SIZE;counter++;}Consumer:

    while ( 1 ) {while ( counter == 0 ); /* do nothing */nextConsumed = buffer[out];out = ( out + 1 ) % BUFFER_SIZE;counter--;/* consume item in nextConsumed */... ... ...

    }If the critical section execution are mutually exclusive in time, then the interleaving of the counter updates will not happen. Then, the answer will always be correct.

  • Critical Section ProblemAssume the following structure for every process Pi:

    While ( not done ) {Remainder sectionCritical section

    Remainder section}

  • Critical Section [CS] ProblemThe critical section problem: How to protect a critical section?Solutions to the CS problem must satisfy:Mutual Exclusion: When process I (Pi) is executing its CS, no other process can execute in its CS.Progress:No process in the remainder section is allowed to block processes wanting to enter their CS.Bounded waiting:Must be able to guarantee that, once Pi requests entry to its CS, no more than k other processes will be allowed to enter before Pi does. We assume nothing about relative speed of the n processes.

  • Solution 1: Take Turns?Code for P0:

    /* Critical section */

    Code for P1:

    /* Critical section */

    Uses a shared integer variable named turn

  • Solution 1: Does it work?Mutual exclusion

    Bounded waiting

    ProgressNeither of the processes can enter until it is their turnThe other process only changes turn when done with CSIf P0 has to wait, P1 enters its CS at most once before P0 gets its chanceIf turn == 1 but P1 is in its remainder section, P0 cannot enter its CS!

  • Solution 2: State intention?Code for P0:

    /* Critical section */

    Uses an array of booleans, flag[]Code for P1:

    /* Critical section */

  • Solution 2: Does it work?Mutual exclusion

    Bounded waiting

    ProgressIf P0 is in CS, then flag[0] == TRUEP1 will never enter CS if flag[0] == TRUESuppose P0 sets flag[0] == TRUEThen P1 sets flag[1] to TRUEBoth P0 and P1 will wait indefinitelySame as above

  • Solution 3: Combination of 1 & 2Code for P0:

    /* Critical section */

    Uses int turn=0; AND boolean flag[2] = {FALSE,FALSE};Code for P1:

    /* Critical section */

  • Proof of Mutual Exclusion in Solution 3Assume towards a contradiction that P0 and P1 are in CS simultaneously.Since P0 and P1 are both in CS, flag = { TRUE, TRUE }.Without loss of generality, assume P1 entered CS at some time t, while P0 was already in its CS.Since flag[0]= TRUE at time t, it must be the case that turn=1 at time t (otherwise P1 could not enter CS).This implies P0 set turn=1 after P1 set turn=0.But if that is true, then P0 would have to wait at the while loop.This means P1 entered its CS before P0 did contradiction!Mutual exclusion follows. P0 and P1 cannot be in CS simultaneously.(See text for proof of bounded wait and progress)

  • Other ways to get mutual exclusionFor more than 2 processes, the CS problem is harder.The hardware can help us achieve mutual exclusion.Idea 1: disable interrupts during the CSSimple and easy to implementRestrictive: No other process in the entire system can runUser processes can have a very long CSBetter idea: an atomic test-and-set instruction

  • Test-and-set instructionPseudocode:

    boolean TestAndSet( boolean *pTarget ) {boolean rv = *pTarget;*pTarget = TRUE;return rv;}

    Basic idea: runs the entire procedure atomically (i.e. disallow interrupts)Can use this to implement a lock for mutual exclusion

  • Mutual Exclusion with TestAndSetCode for P0:

    /* Critical section */

    A lock is also known as a mutex.Code for P1:

    /* Critical section */

    Uses boolean locked; AND TestAndSet function

  • SemaphoresDefinition: A semaphore is an abstract data type with operations allowing mutual exclusion.

    A semaphore S is just an integer that can only be modified by three operations: init(S,i), wait(S), and signal(S)

    init(S,i) {wait(S) {signal(S) { S = i;while( S

  • Mutual Exclusion with Semaphores Code for P0:

    /* Critical section */

    Code for P1:

    /* Critical section */

    Uses a semaphore, semaphore mutex = 1;

  • More on SemaphoresSemaphores are the most common (thus most important) synchronization primitive in used in modern multithreaded applications.You will probably use semaphores in many projects in your future careers!

  • Semaphore ImplementationThe busy-wait in the wait(S) operation is inefficient.Also, to ensure bounded waiting and progress, we need to enforce an ordering on the set of waiting processes.So the implementation of wait(S) actually blocks the calling process and puts it on a queue of processes waiting for S.The implementation of signal(S) unblocks the first waiting process at the head of the queue.So wait() and signal() therefore have their own critical sections.Typically the OS briefly disables interrupts during the critical sections of wait() and signal().

  • MonitorsSemaphores: a low-level synchronization structureWidely used for many synchronization tasksCan be used to solve the critical section problem if wait() and signal() calls are used correctlyBut error-prone. What if I forget to put wait() and signal() around my critical section?Monitors: a higher-level synchronization structureSolves the critical section problem automatically!Can be used for other synchronization tasks too.

  • What is a Monitor?Like a C++ class, or like a C struct that can only be modified by particular functions.

    A monitor has:Some private dataFunctions that manipulate that private dataAccess rules for monitors:Processes cannot directly access internal monitor data.Monitor functions cannot access external data.

  • Monitor Syntaxmonitor monitor-name {shared variable declarations

    procedure body P1 ( . . . ) {. . .}

    procedure body P2 ( . . . ) {. . .}

    . . .{initialization code}}

  • Monitor SemanticsOnly one process at a time can be active within a monitor. This means an entry queue is required.

    Additional variable type: the condition variable. Declared ascondition x, y;

    Condition variables have two operations:x.wait() : suspends calling process until some other process invokes x.signal() x.signal(): resumes one (and only one) suspended process. Has no effect if there are no suspended processes.

  • Monitor Schematic

  • Monitor With Condition Variables

  • Monitor Implementation IssueWhat exactly should happen when P calls x.signal() while Q is waiting on x?We said only one process can be active in a monitor at one time, so we have to enforce either of the following:P waits until Q either leaves the monitor or Q waits for another conditionQ waits until P either leaves the monitor or P waits for another conditionDifferent systems may implement x.signal() either way.

  • Solving Dining Philosophers With a Monitormonitor dp {enum { thinking, hungry, eating } state[5];condition self[5];void pickup(int i);void putdown(int i);void test(int i);void init() {for ( int i=0; i
  • Solving Dining Philosophers With a Monitorvoid pickup(int i) {state[i] = hungry;test(i);if (state[i] != eating)self[i].wait();}}

    void putdown(int i) {state[i] - thinking;test((i+4)%5);test((i+1)%5);}void test(int i) {if ((state[(i+4)%5] != eating) && (state[i] == hungry) && (state[(i+1)%5] != eating)) {state[i] = eating;self[i].signal();}}/*Each philosopher thread/processdoes: */dp.pickup(i); eatdp.putdown(i);

  • Dining Philosophers ExampleTry the following sequence with the monitor solution:T0: Philosopher 0 gets hungryT1: Philosopher 3 gets hungryT2: Philosopher 1 gets hungryT3: Philosopher 0 finished eatingWhat is the state of each philosopher thread and each condition variable at time T4?

  • What have we learned?How race conditions can lead to incorrect results.The critical section (CS) problem:Requirements for solutionsNot-quite-correct software solutionsA correct software solution to the 2-process CS problemA hardware-supported solution to the CS problemMutual exclusion as an important goal:Solutions: TestAndSet, Semaphores, Monitors[Monitors can do much more!]