Mutual Exclusion Techniques for Synchronization in CS470
Learn about mutual exclusion requirements, software and hardware methods, synchronization problems, and concurrency issues in the context of CS470 in this comprehensive overview.
Mutual Exclusion Techniques for Synchronization in CS470
E N D
Presentation Transcript
Synchronization CS470 -- Spring 2002
Overview • Concurrency Problems and Mutual Exclusion Requirements • Software methods with busy-wait • Hardware Support for Mutual Exclusion • Software methods without busy-wait • Semaphores • Message Passing
Concurrency Problems • Interleaved & Overlapped computation - both exhibit similar problems • Ordering access to global resources - leads to reservation of resources • Reservation of resources leads to inefficient use. • Lack of reproducibility makes debugging and testing difficult.
Example • int GetTicket(void) { static customerNbr; return customerNbr++; } Fails to give sequential unduplicated tickets
Vocabulary • Mutual Exclusion protects a critical resource by allowing no more than one thread at a time to execute in a critical section of code which handles that resource. • Mutual exclusion can lead to deadlock or starvation
Win32 Critical Sections • Works for threads in same process • CRITICAL_SECTION data structure • InitializeCriticalSection (&cs) or InitializeCriticalSectionAndSpinCount( &cs, cnt) • EnterCriticalSection (&cs) or TryEnterCriticalSection(&cs) • LeaveCriticalSection(&cs) • Implemented with a semaphore
Mutual Exclusion Requirements • Mutual exclusion - at most one thread at a time can be in the critical section • Threads not in the critical section and not trying to enter it cannot interfere with those trying to enter it • No deadlock or starvation possible if no thread dallies in the critical section
Software Approaches • Standard programming methods are both difficult to code and error prone • Some failed approaches • Dekker’s Algorithm • Peterson’s Algorithm • All make inefficient use of processor because they spin in busy-wait loops.
Attempt 1 - Taking Turns BOOL turn = FALSE; DoThread(BOOL me) { DoNonCritical ( ); while (turn != me) ; DoCriticalSection( ); turn = !me; DoMoreNonCritical( ); } Does Mutual Exclusion without deadlock or starvation; but failure outside of critical section can prevent other thread from entering.
Attempt 2 - checking other BOOL inside[2] = {FALSE, FALSE}; DoThread(BOOL me) { DoNonCritical ( ); while (inside[!me]) ; inside[me] = TRUE; DoCriticalSection ( ); inside[me] = FALSE; DoMoreNonCritical ( ); } Does not guarantee mutual exclusion
Attempt 3 - early locking BOOL inside[2] = {FALSE, FALSE}; DoThread(BOOL me) { DoNonCritical ( ); inside[me] = TRUE; while (inside[!me]) ; DoCriticalSection ( ); inside[me] = FALSE; DoMoreNonCritical ( ); } Just swap two lines…. Does mutual exclusion; but can easily deadlock.
Attempt 4 - intermittant locks DoNonCritical ( ); do { inside[me] = FALSE; Sleep(DELAY); inside[me] = TRUE; } while (inside[!me]); DoCriticalSection ( ); inside[me] = FALSE; DoMoreNonCritical ( ); Mutual exclusion is achieved; but starvation could result.
Peterson’s Algorithm BOOL turn, inside[2] = {FALSE, FALSE}; DoThread(BOOL me) { DoNonCritical ( ); inside[me] = TRUE; turn = !me; while (inside[!me] && turn != me) ; DoCriticalSection ( ); inside[me] = FALSE; DoMoreNonCritical ( ); } Simpler Algorithm
Hardware: Disabling Interrupts • Method: DisableInterrupts( ); DoCriticalSection ( ); EnableInterrupts ( ); • Based on thread context switch being triggered by (clock) interrupt • Works only with uniprocessor • Reduces total system concurrency • Intel: cli and sti instructions Works for n threads
Hardware: Test and Set • Special atomic instruction: BOOL TestSet (BOOL *bitPtr) { BOOL ret = *bitPtr; *bitPtr = TRUE; return ret; } • Intel 386: lock bts mem, reg/imm where 2nd operand is bit offset in first operand
Using Test & Set BOOL bit; DoThread (void *c ) { DoNonCritical ( ); while (TestSet(&bit)) ; DoCriticalSection ( ); bit = FALSE; DoMoreNonCritical ( ); } Starvation could occur; but works for n threads.
Hardware: Exchange • Special Atomic Instruction: Exchange(BOOL *a, BOOL *b) { BOOL temp = *a; *a = *b; *b = temp; } • Intel 386: lock xchg mem, reg
Using Exchange Starvation is possible; but works for n threads. BOOL bit = FALSE; DoThread(void *c) { BOOL key; DoNonCritical( ); key = TRUE; do {Exchange(bit, key); } while (key); DoCriticalSection ( ); bit = FALSE; DoMoreNonCritical ( ); }
Win32 Interlocked Operations • Allows for atomic operations on DWORD by threads in same process or otherwise sharing memory • DWORD target must be at even modulo four address on Intel multiprocessor machines
Interlocked Operation List • InterlockedIncrement(PLONG) • InterlockedDecrement(PLONG) • InterlockedExchange(PLONG target, LONG value) • InterlockedExchangeAdd(PLONG Addend, LONG increment) • InterlockedExchange(PVOID dest, PVOID exchange, PVOID comperand)
Hardware Support Summary • Advantages • Applies to n threads • Simple and easy to verify • Different variables give different locks • Disadvantages • Busy-wait wastes cpu resources • Starvation is possible • Deadlock is possible -- e.g. waiting on lock held by lower priority process.
Counting Semaphores (1 of 2) struct semaphore { DWORD count; ThreadList tList; } void Wait(struct semaphore *s) { if (- - s count < 0) { EnqueueAndBlock(self, s tList); } } Wait is atomic.
Counting Semaphores (2 of 2) void Signal(struct semaphore *s) { if (++s count <= 0) { MoveThreadToRunQueueFrom( s tList); } } Signal is atomic.
Binary Semaphores (1 of 2) struct BSemaphore { BOOL value; ThreadList tList; } void BWait(struct BSemaphore *s) { if (s value == TRUE) { s value = FALSE; } else { EnqueueAndBlock(self, s tList); }} BWait is atomic.
Binary Semaphores (2 of 2) void BSignal(struct BSemaphore *s) { if (s tList == NULL) { s value = TRUE; } else { MoveThreadToRunQueueFrom( s tList); } } BSignal is atomic.
Using Semaphores Could use Binary Semaphores. Could have multiple critical sections, etc. Use TestSet, etc. to implement in operating system. struct semaphore s; DoThread(void *c) { DoNonCritical( ); Wait(&s); DoCriticalSection( ); Signal(&s); DoMoreNonCritical( ); }
Message Passing • Message Operations • Send (destination, message) • Receive (source, message) • Synchronization • Send - blocking or non-blocking • Receive - blocking or non-blocking, may be able to check for arrived messages • typical: non-blocking sends, blocking receives with arrival check
Message Addressing • Direct • Send has explicit address of addressee • Receive • explicit address of sender • implicit, known only after receipt • Indirect via mailboxes • static via permanent ports • dynamic with connect / disconnect • Queuing - FIFO, message priorities
Mutual Exclusion via Messages • Initialize mbox with 1 message • DoThread(void *c) { DoNonCritical ( ); Receive( mbox, message); DoCriticalSection ( ); Send ( mbox, message); DoMoreNonCritical ( ); }
Producer / Consumer (1 of 2) • Initialize mayproduce with n messages, mayconsume as empty • Producer(void *c) { MESSAGE pmsg; while (TRUE) { receive(mayproduce, &pmsg); pmsg = Produce( ); send(mayconsume, &pmsg); } }
Producer / Consumer (2 of 2) • Consumer(void *c) { MESSAGE cmsg; while (TRUE) { Receive(mayconsume, &cmsg); Consume(&cmsg); Send(mayproduce, &cmsg); } } • Allows multiple servers and clients