Download presentation
Presentation is loading. Please wait.
1
CS 3214 Computer Systems
2
MULTI-THREADING CS 3214 Spring 2015
3
Coordinating Multiple Threads
Aside from coordinating access to shared items, threads 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 2015
4
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 2015
5
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 2015
6
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 2015
7
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 2015
8
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 2015
9
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 2015
10
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 2015
11
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 2015
12
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 2015
13
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 2015
14
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 2015
15
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 2015
16
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 2015
17
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 2015
18
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 2015
19
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 2015
20
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 2015
21
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 cases where multiple condition queues are needed CS 3214 Spring 2015
22
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 2015
23
Spurious Wakeups [IEEE Std. 1003.1] says:
When using condition variables there is always a Boolean predicate involving shared variables associated with each condition wait that is true if the thread should proceed. Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur. Since the return from pthread_cond_timedwait() or pthread_cond_wait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return. Also true, btw, for java.lang.Object.wait() This implies a rechecking (while-loop) of the predicate is necessary even if there are only 2 threads. CS 3214 Spring 2015
24
Spurious Wakeups, cont’d
See also [IEEE ] “An added benefit of allowing spurious wakeups is that applications are forced to code a predicate-testing-loop around the condition wait. This also makes the application tolerate superfluous condition broadcasts or signals on the same condition variable that may be coded in some other part of the application. The resulting applications are thus more robust. Therefore, POSIX explicitly documents that spurious wakeups may occur.” CS 3214 Spring 2015
25
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 rendezvous, 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 2015
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.