Download presentation
Presentation is loading. Please wait.
Published byGarey Dickerson Modified over 6 years ago
1
CS 3214 Computer Systems Lecture 22 Godmar Back
2
Announcements Exercise 10 due Apr 19 Project 5 out later today
CS 3214 Spring 2010 7/22/2018
3
MULTI-THREADING CS 3214 Spring 2010 7/22/2018
4
Rules for Easy Locking Every shared variable must be protected by a lock Establish this relationship with code comments /* protected by … <lock>*/ Acquire lock before touching (reading or writing) variable Release when done, on all paths One lock may protect more than one variable, but not too many If in doubt, use fewer locks (may lead to worse efficiency, but less likely to lead to race conditions or deadlock) If manipulating multiple variables, acquire locks assigned to protecting each Acquire locks always in same order (doesn’t matter which order, but must be same) Release in opposite order Don’t release any locks before all have been acquired (two-phase locking) CS 3214 Spring 2010 7/22/2018
5
Mapping Locks To Variables
Choosing which lock should protect which shared variable(s) is not easy – must weigh: Whether all variables are always accessed together (use one lock if so) Whether there is an atomicity requirement if multiple variables are accessed in related sequence (must hold single lock if so) Cost of multiple calls to lock/unlock (advantage of increased parallelism may be offset by those costs) Whether code inside critical section may block (if not, no throughput gain from fine-grained locking on uniprocessor) CS 3214 Spring 2010 7/22/2018
6
Race Detection Tools A number of tools help to detect race conditions (Helgrind, Intel Thread Checker) Dynamic analysis tools Typically based one or both of these approaches: Locksets: detect if no lock is consistently held when a given variable x is accessed “Happens-before” relationship: (intuition) if we can’t prove that access A must happen before B due to the synchronization the programmer used, then it’s a race; example: All accesses before a thread is started “happen before” all accesses by a thread; and these accesses “happen before” all accesses done after the thread is joined Typically do not detect atomicity violations CS 3214 Spring 2010 7/22/2018
7
Coordinating Multiple Threads
Aside from coordinating access to shared items, thread may need to communicate about events “has event A already happened in another thread?” aka “precedence constraint”, or “scheduling constraint” Do B after A Must do so Correctly (never miss that event A has occurred when in fact it has) Efficiently Don’t waste resources in the process Don’t unnecessarily delay notification of event A CS 3214 Spring 2010 7/22/2018
8
int coin_flip; static void * thread1(void *_) { coin_flip = rand() % 2; printf("Thread 1: flipped coin %d\n", coin_flip); return NULL; } thread2(void *_) printf("Thread 2: flipped coin %d\n", int main() { int i, N = 2; pthread_t t[N]; srand(getpid()); pthread_create(&t[1], NULL, thread2, NULL); pthread_create(&t[0], NULL, thread1, NULL); for (i = 0; i < N; i++) pthread_join(t[i], NULL); return 0; } Q.: How can thread2 make sure that ‘coin_flip’ has occurred before printing its outcome? CS 3214 Spring 2010 7/22/2018
9
Thread 2 could “busy-wait” – spin until thread 1 completes the coin flip. Exceptions not withstanding, this is practically never an acceptable solution. int coin_flip; volatile bool coin_flip_done; static void * thread1(void *_) { coin_flip = rand() % 2; coin_flip_done = true; printf("Thread 1: flipped coin %d\n", coin_flip); return NULL; } The somewhat less wasteful variant of busy-waiting: while (!coin_flip_done) sched_yield(); is not acceptable, either. wastes CPU cycles is fragile (volatile needed when using –O) does not document semantics static void * thread2(void *_) { /* Thread 2 spins, "busy-waits" until the coin flip is done. * This is an unacceptable solution. Bad for the planet, too. */ while (!coin_flip_done) continue; printf("Thread 2: flipped coin %d\n", coin_flip); return NULL; } CS 3214 Spring 2010 7/22/2018
10
Semaphores Invented by Edsger Dijkstra in 1960s
Source: inter.scoutnet.org Invented by Edsger Dijkstra in 1960s Counter S, initialized to some value, with two operations: P(S) or “down” or “wait” – if counter greater than zero, decrement. Else wait until greater than zero, then decrement V(S) or “up” or “signal” or “post” – increment counter, wake up any threads stuck in P. Semaphores don’t go negative: #V + InitialValue - #P >= 0 Note: direct access to counter value after initialization is not allowed Counting Semaphores vs Binary Semaphores Binary: counter can only be 0 or 1 Simple to implement, yet powerful Can be used for many synchronization problems CS 3214 Spring 2010 7/22/2018
11
POSIX Semaphores int coin_flip;
sem_t coin_flip_done; // semaphore for thread 1 to signal coin flip static void * thread1(void *_) { coin_flip = rand() % 2; sem_post(&coin_flip_done); // raise semaphore, increment, 'up' printf("Thread 1: flipped coin %d\n", coin_flip); } POSIX Semaphores Notice the 3rd argument of sem_init() – it gives the initial value of the semaphore: ‘0’ means the semaphore is used to express scheduling constraint static void * thread2(void *_) { // wait until semaphore is raised, // then decrement, 'down' sem_wait(&coin_flip_done); printf("Thread 2: flipped coin %d\n", coin_flip); } int main() { … sem_init(&coin_flip_done, 0, 0); pthread_create(&t[1], NULL, thread2, NULL); pthread_create(&t[0], NULL, thread1, NULL); } CS 3214 Spring 2010 7/22/2018
12
Implementing Mutual Exclusion with Semaphores
Semaphores can be used to build locks Must initialize semaphore with 1 to allow one thread to enter critical section This is not a recommended style, despite of what Bryant & O’Hallaron suggest – you should use a mutex instead [Cantrill & Bonwick 2008] sem_t S; sem_init(&S, 0, 1); lock_acquire() { // try to decrement, wait if 0 sem_wait (S); } lock_release() { // increment (wake up waiters if any) sem_post(S); Easily generalized to allow at most N simultaneous threads: multiplex pattern (i.e., a resource can be accessed by at most N threads) CS 3214 Spring 2010 7/22/2018
13
Condition Variables - Intro
Besides (and perhaps more so) than semaphores, condition variables are another widely used form to implement ‘signaling’ kinds of coordination/synchronization In POSIX Threads, Java, C# Based on the concept of a Monitor ADT that combines protected access to state and signaling Confusing terminology alert: Word ‘signal’ is overloaded 3 times Semaphore signal (V(), “up”, “post”) Monitor/Condition variable signal (“signal”, “notify”) Unix signals Word ‘wait’ is overloaded Semaphore wait (P(), “down”) Monitor/Condition variable wait Unix wait() for child process CS 3214 Spring 2010 7/22/2018
14
Monitors A monitor combines a set of shared variables & operations to access them Think of a Java class with no public fields & all public methods carrying the attribute ‘synchronized’ A monitor provides implicit synchronization (only one thread can access private variables simultaneously) Single lock is used to ensure all code associated with monitor is within critical section A monitor provides a general signaling facility Wait/Signal pattern (similar to, but different from semaphores) May declare & maintain multiple signaling queues CS 3214 Spring 2010 7/22/2018
15
Monitors (cont’d) Classic monitors are embedded in programming languages Invented by Hoare & Brinch-Hansen 1972/73 First used in Mesa/Cedar Xerox PARC 1978 Adapted version available in Java/C# (Classic) Monitors are safer than semaphores can’t forget to lock data – compiler checks this In contemporary C, monitors are a synchronization pattern that is achieved using locks & condition variables Helps to understand monitor abstraction to use it correctly CS 3214 Spring 2010 7/22/2018
16
Infinite Buffer w/ Monitor
monitor buffer { /* implied: struct lock mlock;*/ private: char buffer[]; int head, tail; public: produce(item); item consume(); } buffer::produce(item i) { /* try { lock_acquire(&mlock); */ buffer[head++] = i; /* } finally {lock_release(&mlock);} */ } buffer::consume() return buffer[tail++]; Monitors provide implicit protection for their internal variables Still need to add the signaling part CS 3214 Spring 2010 7/22/2018
17
Condition Variables Used by a monitor for signaling a condition
a general (programmer-defined) condition, not just integer increment as with semaphores Somewhat weird: the condition is actually not stored in the variable – it’s typically some boolean predicate of monitor variables, e.g. “buffer.size > 0” the condition variable itself is better thought of as a signaling queue Monitor can have more than one condition variable Three operations: Wait(): leave monitor, wait for condition to be signaled, reenter monitor Signal(): signal one thread waiting on condition Broadcast(): signal all threads waiting on condition CS 3214 Spring 2010 7/22/2018
18
Condition Variables as Queues
Region of mutual exclusion Enter Exit Wait Signal A condition variable’s state is just a queue of waiters: Wait(): adds current thread to (end of queue) & block Signal(): pick one thread from queue & unblock it Broadcast(): unblock all threads Note on style: best practice is to leave monitor only once, and near the procedure’s entry. CS 3214 Spring 2010 7/22/2018
19
Bounded Buffer w/ Monitor
monitor buffer { condition items_avail; condition slots_avail; private: char buffer[]; int head, tail; public: produce(item); item consume(); } buffer::produce(item i) { while ((tail+1–head)%CAPACITY==0) slots_avail.wait(); buffer[head++] = i; items_avail.signal(); } buffer::consume() while (head == tail) items_avail.wait(); item i = buffer[tail++]; slots_avail.signal(); return i; CS 3214 Spring 2010 7/22/2018
20
Bounded Buffer w/ Monitor
monitor buffer { condition items_avail; condition slots_avail; private: char buffer[]; int head, tail; public: produce(item); item consume(); } buffer::produce(item i) { while ((tail+1–head)%CAPACITY==0) slots_avail.wait(); buffer[head++] = i; items_avail.signal(); } buffer::consume() while (head == tail) items_avail.wait(); item i = buffer[tail++]; slots_avail.signal(); return i; Q1.: How is lost update problem avoided? lock_release(&mlock); block_on(items_avail); lock_acquire(&mlock); Q2.: Why while() and not if()? CS 3214 Spring 2010 7/22/2018
21
cond_signal semantics
cond_signal keeps lock, so it leaves signaling thread in monitor waiter is made READY, but can’t enter until signaler gives up lock There is no guarantee whether signaled thread will enter monitor next or some other thread will (who may be already waiting!) so must always use “while()” when checking condition – cannot assume that condition set by signaling thread will still hold when monitor is reentered by signaled thread This semantics is also referred to as “Mesa-Style” after the system in which it was first used POSIX Threads, Java, and C# use this semantics CS 3214 Spring 2010 7/22/2018
22
Condition Variables vs. Semaphores
Signals are lost if nobody’s on the queue (e.g., nothing happens) Wait() always blocks Semaphores Signals (calls to V() or sem_post()) are remembered even if nobody’s current waiting Wait (e.g., P() or sem_wait()) may or may not block CS 3214 Spring 2010 7/22/2018
23
Monitors in C POSIX Threads as well as many custom environments
No compiler support, must do it manually must declare locks & condition vars must call pthread_mutex_lock/unlock when entering & leaving the monitor must use pthread_cond_wait/pthread_cond_signal to wait for/signal condition Note: pthread_cond_wait(&c, &m) takes monitor lock as parameter necessary so monitor can be left & reentered without losing signals pthread_cond_signal() does not leaving room for programmer error! CS 3214 Spring 2010 7/22/2018
24
Monitors in Java synchronized block means
class buffer { private char buffer[]; private int head, tail; public synchronized produce(item i) { while (buffer_full()) this.wait(); buffer[head++] = i; this.notifyAll(); } public synchronized item consume() { while (buffer_empty()) i = buffer[tail++]; return ; synchronized block means enter monitor execute block leave monitor wait()/notify() use condition variable associated with receiver Every object in Java can function as a condition variable (just like it can function as a lock) More restrictive than Pthreads/C which allow multiple condition variables (signaling conditions) to be used in connection with a lock protecting state CS 3214 Spring 2010 7/22/2018
25
import java.util.concurrent.locks.*;
class buffer { private ReentrantLock monitorlock = new ReentrantLock(); private Condition items_available = monitorlock.newCondition(); private Condition slots_available public /* NO SYNCHRONIZED here */ void produce(item i) { monitorlock.lock(); try { while (buffer_full()) slots_available.await(); buffer[head++] = i; items_available.signal(); } finally { monitorlock.unlock(); } } /* consume analogous */ Monitors in Java, Take 2 Previous slide (bounded buffer) is actually an example of where Java’s built-in monitors suck Needed “notifyAll()” to make sure one at least one of the right kind of threads was woken up Unacceptably inefficient Use java.util.concurrent.- locks.Condition instead in case where multiple condition queues are needed CS 3214 Spring 2010 7/22/2018
26
A ReadWrite Lock Implementation
struct lock mlock; // protects rdrs & wrtrs int readers = 0, writers = 0; struct condvar canread, canwrite; void read_lock_acquire() { lock_acquire(&mlock); while (writers > 0) cond_wait(&canread, &mlock); readers++; lock_release(&mlock); } void read_lock_release() { if (--readers == 0) cond_signal(&canwrite); void write_lock_acquire() { lock_acquire(&mlock); while (readers > 0 || writers > 0) cond_wait(&canwrite, &mlock); writers++; lock_release(&mlock); } void write_lock_release() { writers--; ASSERT(writers == 0); cond_broadcast(&canread); cond_signal(&canwrite); Note: this is a naïve implementation that may lead to livelock – no guarantees a reader or writer ever enters the locked section even if every threads eventually leaves it CS 3214 Spring 2010 7/22/2018
27
Summary Semaphores & Condition Variables provide signaling facilities
Condition variables are loosely based on “monitor” concept Java/C# provide syntactic sugar Semaphores have “memory” But require that # of signals matches # of waits Good for rendez-vous, precedence constraints – if problem lends itself to semaphore, use one Always use idiomatic “while (!cond) *_wait()” pattern when using condition variables (in C) or Object.wait() (in Java) CS 3214 Spring 2010 7/22/2018
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.