synchronization
play

Synchronization CISC3595/5595 Fall 2015 Fordham Univ. - PowerPoint PPT Presentation

Synchronization CISC3595/5595 Fall 2015 Fordham Univ. Synchronization Motivation When threads concurrently read/write shared memory, program behavior is undefined Two threads write to the same variable; which one should win?


  1. Synchronization CISC3595/5595 Fall 2015 Fordham Univ.

  2. Synchronization Motivation • When threads concurrently read/write shared memory, program behavior is undefined – Two threads write to the same variable; which one should win? • Thread schedule is non-deterministic – Behavior changes when re-run program • Compiler/hardware instruction reordering • Multi-word operations are not atomic

  3. Question: Can this panic? Thread 1 Thread 2 � � p = someComputation(); while (!pInitialized) pInitialized = true; ; q = someFunction(p); if (q != someFunction(p)) panic

  4. Why Reordering? • Why do compilers reorder instructions? – Efficient code generation requires analyzing control/data dependency – If variables can spontaneously change, most compiler optimizations become impossible • Why do CPUs reorder instructions? – Write buffering: allow next instruction to execute while write is being completed Fix: memory barrier – Instruction to compiler/CPU – All ops before barrier complete before barrier returns – No op after barrier starts until barrier returns

  5. Too Much Milk Example Person A Person B 12:30 Look in fridge. Out of milk. 12:35 Leave for store. 12:40 Arrive at store. Look in fridge. Out of milk. 12:45 Buy milk. Leave for store. 12:50 Arrive home, put milk Arrive at store. away. 12:55 Buy milk. 1:00 Arrive home, put milk away. Oh no!

  6. Definitions Race condition: output of a concurrent program depends on the order of operations between threads Mutual exclusion: only one thread does a particular thing at a time – Critical section: piece of code that only one thread can execute at once Lock: prevent someone from doing something – Lock before entering critical section, before accessing shared data – Unlock when leaving, after done accessing shared data – Wait if locked (all synchronization involves waiting!)

  7. Too Much Milk, Try #1 • Correctness property – Someone buys if needed (liveness) – At most one person buys (safety) • Try #1: leave a note if (!note) if (!milk) { leave note buy milk remove note }

  8. Too Much Milk, Try #2 Thread A Thread B � � leave note A leave note B if (!note B) { if (!noteA) { if (!milk) if (!milk) buy milk buy milk } } remove note A remove note B

  9. Too Much Milk, Try #3 Thread A Thread B � � leave note A leave note B while (note B) // X if (!noteA) { // Y do nothing; if (!milk) if (!milk) buy milk buy milk; } remove note A remove note B Can guarantee at X and Y that either: (i) Safe for me to buy (ii)Other will buy, ok to quit

  10. Lessons • Solution is complicated – “obvious” code often has bugs • Modern compilers/architectures reorder instructions – Making reasoning even more difficult • Generalizing to many threads/processors – Even more complex: see Peterson’s algorithm

  11. Structured Synchronization 11

  12. Example: Shared Object 12

  13. 13

  14. 14

  15. 15

  16. 16

  17. Roadmap: Layered View 17

  18. Locks • Lock::acquire – wait until lock is free, then take it • Lock::release – release lock, waking up anyone waiting for it 1. At most one lock holder at a time (safety) 2. If no one holding, acquire gets lock (progress) 3. If all lock holders finish and no higher priority waiters, waiter eventually gets lock (progress)

  19. Question: Why only Acquire/Release • Suppose we add a method to a lock, to ask if the lock is free. Suppose it returns true. Is the lock: – Free? – Busy? – Don’t know?

  20. Too Much Milk, #4 Locks allow concurrent code to be much simpler: � lock.acquire(); if (!milk) buy milk lock.release();

  21. Lock Example: Malloc/Free char *malloc (n) { void free(char *p) { heaplock.acquire(); heaplock.acquire(); p = allocate memory put p back on free list heaplock.release(); heaplock.release(); return p; } }

  22. Rules for Using Locks • Lock is initially free • Always acquire before accessing shared data structure – Beginning of procedure! • Always release after finishing with shared data – End of procedure! – Only the lock holder can release – DO NOT throw lock for someone else to release • Never access shared data without lock – Danger!

  23. Will this code work? if (p == NULL) { newP() { lock.acquire(); p = malloc(sizeof(p)); if (p == NULL) { p->field1 = … p = newP(); p->field2 = … } return p; lock.release(); } } use p->field1

  24. Example: Bounded Buffer tryget() { tryput(item) { item = NULL; lock.acquire(); lock.acquire(); if ((tail – front) < size) { if (front < tail) { buf[tail % MAX] = item; item = buf[front % MAX]; tail++; front++; } } lock.release(); lock.release(); } return item; • If tryget returns NULL, do we know the buffer is empty? } If we poll tryget in a loop, what happens to a thread calling tryput • Initially: front = tail = 0; lock = FREE; MAX is buffer capacity If tryget returns NULL, do we know the buffer is empty? If we poll tryget in a loop, what happens to a thread calling tryput?

  25. Suppose we want to block? 25

  26. Condition Variables • Waiting inside a critical section – Called only when holding a lock � • Wait: atomically release lock and relinquish processor – Reacquire the lock when wakened • Signal: wake up a waiter, if any • Broadcast: wake up all waiters, if any

  27. Condition Variable Design Pattern methodThatWaits() { methodThatSignals() { lock.acquire(); lock.acquire(); // Read/write shared state // Read/write shared state � while (!testSharedState()) { // If testSharedState is now true cv.wait(&lock); cv.signal(&lock); } � � // Read/write shared state // Read/write shared state lock.release(); lock.release(); } }

  28. Example: Bounded Buffer get() { put(item) { lock.acquire(); lock.acquire(); while (front == tail) { while ((tail – front) == MAX) { empty.wait(lock); full.wait(lock); } } item = buf[front % MAX]; buf[tail % MAX] = item; front++; tail++; full.signal(lock); empty.signal(lock); lock.release(); lock.release(); return item; } } Initially: front = tail = 0; MAX is buffer capacity empty/full are condition variables

  29. Pre/Post Conditions • What is state of the bounded buffer at lock acquire? – front <= tail – front + MAX >= tail • These are also true on return from wait • And at lock release • Allows for proof of correctness

  30. Pre/Post Conditions methodThatWaits() { methodThatSignals() { lock.acquire(); lock.acquire(); // Pre-condition: State is consistent // Pre-condition: State is consistent � � // Read/write shared state // Read/write shared state � while (!testSharedState()) { // If testSharedState is now true cv.wait(&lock); cv.signal(&lock); � } // WARNING: shared state may // NO WARNING: signal keeps lock � // have changed! But // testSharedState is TRUE // Read/write shared state // and pre-condition is true lock.release(); � } // Read/write shared state lock.release(); }

  31. Condition Variables • ALWAYS hold lock when calling wait, signal, broadcast – Condition variable is sync FOR shared state – ALWAYS hold lock when accessing shared state • Condition variable is memoryless – If signal when no one is waiting, no op – If wait before signal, waiter wakes up • Wait atomically releases lock – What if wait, then release? – What if release, then wait?

  32. Condition Variables, cont’d • When a thread is woken up from wait, it may not run immediately – Signal/broadcast put thread on ready list – When lock is released, anyone might acquire it • Wait MUST be in a loop while (needToWait()) { condition.Wait(lock); } • Simplifies implementation – Of condition variables and locks – Of code that uses condition variables and locks

  33. Java Manual When waiting upon a Condition, a “spurious wakeup” is permitted to occur, in general, as a concession to the underlying platform semantics. This has little practical impact on most application programs as a Condition should always be waited upon in a loop, testing the state predicate that is being waited for.

  34. Semaphores • Semaphore has a non-negative integer value – P() atomically waits for value to become > 0, then decrements – V() atomically increments value (waking up waiter if needed) • Semaphores are like integers except: – Only operations are P and V – Operations are atomic • If value is 1, two P’s will result in value 0 and one waiter • Semaphores are useful for – Unlocked wait: interrupt handler, fork/join

  35. Semaphore Bounded Buffer get() { put(item) { fullSlots.P(); emptySlots.P(); mutex.P(); mutex.P(); item = buf[front % MAX]; buf[last % MAX] = item; front++; last++; mutex.V(); mutex.V(); emptySlots.V(); fullSlots.V(); return item; } } Initially: front = last = 0; MAX is buffer capacity mutex = 1; emptySlots = MAX; fullSlots = 0;

  36. Implementing Condition Variables using Semaphores (Take 1) wait(lock) { lock.release(); semaphore.P(); lock.acquire(); } signal() { semaphore.V(); }

  37. Roadmap: Layered View 37

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend