310 likes | 443 Views
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.
E N D
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