0% found this document useful (0 votes)
10 views

Concurrency II: Synchronization Nov 14, 2000: "The Course That Gives CMU Its Zip!"

This document discusses synchronization techniques for concurrent threads accessing shared variables. It begins with an example of unsynchronized access to a shared counter variable leading to incorrect results. Progress graphs and the concept of critical sections are introduced to analyze concurrent execution and identify unsafe interleavings. Semaphores are then presented as a classic solution, using P and V operations to guarantee a safe trajectory by preventing threads from entering unsafe regions. Examples show how semaphores can be used for mutual exclusion on a shared variable and in a producer-consumer pattern. Limitations of the semaphore approach are also noted.

Uploaded by

wasimakramakram
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views

Concurrency II: Synchronization Nov 14, 2000: "The Course That Gives CMU Its Zip!"

This document discusses synchronization techniques for concurrent threads accessing shared variables. It begins with an example of unsynchronized access to a shared counter variable leading to incorrect results. Progress graphs and the concept of critical sections are introduced to analyze concurrent execution and identify unsafe interleavings. Semaphores are then presented as a classic solution, using P and V operations to guarantee a safe trajectory by preventing threads from entering unsafe regions. Examples show how semaphores can be used for mutual exclusion on a shared variable and in a producer-consumer pattern. Limitations of the semaphore approach are also noted.

Uploaded by

wasimakramakram
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 34

15-213

The course that gives CMU its Zip!

Concurrency II: Synchronization Nov 14, 2000


Topics
Progress graphs Semaphores Mutex and condition variables Barrier synchronization Timeout waiting

class23.ppt

A version of badcnt.c with a simple counter loop


int ctr = 0; /* shared */ /* main routine creates*/ /* two count threads */ /* count thread */ void *count(void *arg) { int i; for (i=0; i<NITERS; i++) ctr++; return NULL; }

note: counter should be equal to 200,000,000


linux> badcnt BOOM! ctr=198841183 linux> badcnt BOOM! ctr=198261801 linux> badcnt BOOM! ctr=198269672

What went wrong?


class23.ppt 2 CS 213 F00

Assembly code for counter loop


C code for counter loop
for (i=0; i<NITERS; i++) ctr++; .L9:

Corresponding asm code (gcc -O0 -fforce-mem)


movl -4(%ebp),%eax cmpl $99999999,%eax jle .L12 jmp .L10 .L12: movl ctr,%eax # Load leal 1(%eax),%edx # Update movl %edx,ctr # Store .L11: movl -4(%ebp),%eax leal 1(%eax),%edx movl %edx,-4(%ebp) jmp .L9 .L10:

Head (Hi)

Load ctr (Li) Update ctr (Ui) Store ctr (Si) Tail (Ti)

class23.ppt

CS 213 F00

Concurrent execution
Key thread idea: In general, any sequentially consistent interleaving is possible, but some are incorrect!
Ii denotes that thread i executes instruction I %eaxi is the contents of %eax in thread is context
i (thread) 1 1 1 1 2 2 2 2 2 1 instri H1 L1 U1 S1 H2 L2 U2 S2 T2 T1 %eax1 0 1 1 1 %eax2 1 2 2 2 ctr 0 0 0 1 1 1 1 2 2 2

OK
CS 213 F00

class23.ppt

Concurrent execution (cont)


Incorrect ordering: two threads increment the counter, but the result is 1 instead of 2.

i (thread) 1 1 1 2 2 1 1 2 2 2

instri H1 L1 U1 H2 L2 S1 T1 U2 S2 T2

%eax1 0 1 1 1 -

%eax2 0 1 1 1

ctr 0 0 0 0 0 1 1 1 1 1

Oops!

class23.ppt

CS 213 F00

Concurrent execution (cont)


How about this ordering?
i (thread) 1 1 2 2 2 2 1 1 1 2 instri H1 L1 H2 L2 U2 S2 U1 S1 T1 T2 %eax1 %eax2 ctr

We can clarify our understanding of concurrent execution with the help of the progress graph
class23.ppt 6 CS 213 F00

Progress graphs
Thread 2 T2 S2 (L1, S2)

A progress graph depicts the discrete execution state space of concurrent threads. Each axis corresponds to the sequential order of instructions in a thread. Each point corresponds to a possible execution state (Inst1, Inst2). E.g., (L1, S2) denotes state where thread 1 has completed L1 and thread 2 has completed S2.

U2 L2 H2

(O,O)

H1

L1

U1

S1

T1

Thread 1

class23.ppt

CS 213 F00

Legal state transitions


Interleaved concurrent execution (one processor):
or

Parallel concurrent execution (multiple processors)


or or (parallel execution)

Key point: Always reason about concurrent threads as if each thread had its own CPU.
class23.ppt 8 CS 213 F00

Trajectorie s
Thread 2 T2 S2

A trajectory is a sequence of legal state transitions that describes one possible concurrent execution of the threads. Example:

U2 L2 H2

H1, L2, U1, H2, L2, S1, T1, U2, S2, T2

(O,O)

H1

L1

U1

S1

T1

Thread 1

class23.ppt

CS 213 F00

Critical sections and unsafe regions


Thread 2 T2

Unsafe region critical section wrt shared variable ctr


S2

L, U, and S form a critical section with respect to the shared variable ctr. Instructions in critical sections (wrt to some shared variable) should not be interleaved. Sets of states where such interleaving occurs form unsafe regions.

U2 L2 H2

(O,O)

H1

L1

U1

S1

T1

Thread 1

critical section wrt shared variable ctr


class23.ppt 10 CS 213 F00

Thread 2 T2

Safe trajectories
Unsafe region Def: A safe trajectory is a sequence of legal transitions that does not touch any states in an unsafe region. Claim: Any safe trajectory results in a correct value for the shared variable ctr.

S2

critical section

U2 L2 H2

(O,O)

H1

L1

U1

S1

T1

Thread 1

critical section
class23.ppt 11 CS 213 F00

Unsafe trajectories
Thread 2 T2

Touching a state of type x is always incorrect. Touching a state of type y may or may not be OK: correct because store completes before load. incorrect because order of load and store are indeterminate. Moral: be conservative and disallow all unsafe trajectories.

Unsafe region
S2 y x x y x x y y

critical section

U2 L2 H2

(O,O)

H1

L1

U1

S1

T1 Thread 1

critical section
class23.ppt 12 CS 213 F00

Semaphore operations
Question: How can we guarantee a safe trajectory?
We must synchronize the threads so that they never enter an unsafe state.

Classic solution: Dijkstra's P and V operations on semaphores.


semaphore: non-negative integer synchronization variable. P(s): [ while (s == 0) wait(); s--; ] Dutch for "Proberen" (test) V(s): [ s++; ] Dutch for "Verhogen" (increment) OS guarantees that operations between brackets [ ] are executed indivisibly. Only one P or V operation at a time can modify s. When while loop in P terminates, only that P can decrement s. Semaphore invariant: (s >= 0)

class23.ppt

13

CS 213 F00

Sharing with semaphores


Thread 2 T2

s=1
V(s)

s=0

s=1

ctr++; P(s) H2

s=0

s = -1 (forbidden s = 0 region)

Provide mutually exclusive access to shared variable by surrounding critical section with P and V operations on semaphore s (initially set to 1). Semaphore invariant creates a forbidden region that encloses unsafe region and is never touched by any trajectory. Semaphore used in this way is often called a mutex (mutual exclusion).

s=1

s=0

s=1

(O,O)

H1

P(s) ctr++; V(s)

Initially, s = 1
class23.ppt

T1 Thread 1

14

CS 213 F00

Posix semaphores /* initialize semaphore sem to value */


/* pshared=0 if thread, pshared=1 if process */ void Sem_init(sem_t *sem, int pshared, unsigned int value) { if (sem_init(sem, pshared, value) < 0) unix_error("Sem_init"); } /* P operation on semaphore sem */ void P(sem_t *sem) { if (sem_wait(sem)) unix_error("P"); } /* V operation on semaphore sem */ void V(sem_t *sem) { if (sem_post(sem)) unix_error("V"); }

class23.ppt

15

CS 213 F00

Sharing with Posix semaphores


/* goodcnt.c - properly synch'd */ /* version of badcnt.c */ #include <ics.h> #define NITERS 10000000 void *count(void *arg); struct { int ctr; /* shared ctr */ sem_t mutex; /* semaphore */ } shared; int main() { pthread_t tid1, tid2; /* init mutex semaphore to 1 */ Sem_init(&shared.mutex, 0, 1); /* create 2 ctr threads and wait */ ... }
class23.ppt 16 CS 213 F00

/* counter thread */ void *count(void *arg) { int i; for (i=0; i<NITERS; i++) { P(&shared.mutex); shared.ctr++; V(&shared.mutex); } return NULL; }

Progress graph for goodcnt.c


Thread 2 V(m) f.r. P(m) V(m) f.r. P(m) V(m) f.r. P(m)

P(m)

V(m)

P(m)

V(m)

P(m)

V(m)

Thread 1

Initially, mutex = 1
class23.ppt 17 CS 213 F00

Deadloc k
Thread 2 V(s) forbidden region for s V(t)

deadlock state

Semaphores introduce the potential for deadlock: waiting for a condition that will never be true. Any trajectory that enters the deadlock region will eventually reach the deadlock state, waiting for either s or t to become nonzero. Other trajectories luck out and skirt the deadlock region.

P(s) deadlock region P(t) forbidden region for t

P(s)

P(t)

V(s)

V(t) Thread 1

Initially, s=t=1
class23.ppt 18

Unfortunate fact: deadlock is often non-deterministic.


CS 213 F00

A deterministic deadlock
Thread 2 V(t) f.r. for t P(t) f.r. for t f.r. for s P(s) deadlock region deadlock state

... Sometimes though, we get "lucky" and the deadlock is deterministic. Here is an example of a deterministic deadlock caused by improperly initializing semaphore t. Problem: correct this program and draw the resulting forbidden regions.
P(t) V(t) Thread 1

...

V(s)

P(s)

V(s)

Initially, s = 1, t = 0.
class23.ppt 19 CS 213 F00

Signaling with semaphores


producer thread shared buffer consumer thread

Common synchronization pattern:


Producer waits for slot, inserts item in buffer, and signals consumer. Consumer waits for item, removes it from buffer, and signals producer.

Examples
Multimedia processing: producer creates MPEG video frames, consumer renders the frames Graphical user interfaces producer detects mouse clicks, mouse movements, and keyboard hits and inserts corresponding events in buffer. consumer retrieves events from buffer and paints the display.

class23.ppt

20

CS 213 F00

Producer-consumer (1buffer)
/* buf1.c - producer-consumer on 1-element buffer */ #include <ics.h> #define NITERS 5 void *producer(void *arg); void *consumer(void *arg); struct { int buf; /* shared var */ sem_t full; /* sems */ sem_t empty; } shared; int main() { pthread_t tid_producer; pthread_t tid_consumer; /* initialize the semaphores */ Sem_init(&shared.empty, 0, 1); Sem_init(&shared.full, 0, 0); /* create threads and wait */ Pthread_create(&tid_producer, NULL, producer, NULL); Pthread_create(&tid_consumer, NULL, consumer, NULL); Pthread_join(tid_producer, NULL); Pthread_join(tid_consumer, NULL); exit(0); }

class23.ppt

21

CS 213 F00

Producer-consumer (cont) Initially: empty = 1, full = 0.


/* producer thread */ void *producer(void *arg) { int i, item; for (i=0; i<NITERS; i++) { /* produce item */ item = i; printf("produced %d\n", item); /* write item to buf */ P(&shared.empty); shared.buf = item; V(&shared.full); } return NULL; }
class23.ppt 22 CS 213 F00

/* consumer thread */ void *consumer(void *arg) { int i, item; for (i=0; i<NITERS; i++) { /* read item from buf */ P(&shared.full); item = shared.buf; V(&shared.empty); /* consume item */ printf("consumed %d\n", item); } return NULL; }

Producer-consumer progress graph Consumer


V(e) P(f) V(e) P(f) V(e) P(f)

The forbidden regions prevent the producer from writing into a full buffer. They also prevent the consumer from reading an empty buffer. Problem: Write version for n-element buffer with multiple producers and consumers.
P(e) V(f) P(e) V(f) P(e) V(f) Producer
23 CS 213 F00

Initially, empty = 1, full = 0.


class23.ppt

Limitations of semaphores
Semaphores are sound and fundamental, but they have limitations.
Difficult to broadcast a signal to a group of threads. e.g., barrier synchronization: no thread returns from the barrier function until every other thread has called the barrier function. Impossible to do timeout waiting. e.g., wait for at most 1 second for a condition to become true.

For these we must use Pthreads mutex and condition variables.

class23.ppt

24

CS 213 F00

Basic operations on mutex variables int pthread_mutex_init(pthread_mutex_t *mutex,


pthread_mutexattr_t *attr)

Initializes a mutex variable (mutex) with some attributes (attr). attributes are usually NULL. like initializing a mutex semaphore to 1.
int pthread_mutex_lock(pthread_mutex_t *mutex)

Indivisibly waits for mutex to be unlocked and then locks it. like P(mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)

Unlocks mutex. like V(mutex)


class23.ppt 25 CS 213 F00

Basic operations on condition variables


int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr)

Initializes a condition variable (cond) with some attributes (attr). attributes are usually NULL.
int pthread_cond_signal(pthread_cond_t *cond)

Awakens one thread waiting on condition cond. if no threads waiting on condition, then it does nothing. key point: signals are not queued!
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)

Indivisibly unlocks mutex and waits for signal on condition cond When awakened, indivisibly locks mutex.
class23.ppt

26

CS 213 F00

Advanced operations on condition variables


int pthread_cond_broadcast(pthread_cond_t *cond)

Awakens all threads waiting on condition cond. if no threads waiting on condition, then it does nothing.
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *abstime)

Waits for condition cond until absolute wall clock time is abstime Unlocks mutex on entry, locks mutex on awakening. Use of absolute time rather than relative time is strange.

class23.ppt

27

CS 213 F00

Signaling and waiting on conditions


Basic pattern for signaling
Pthread_mutex_lock(&mutex); Pthread_cond_signal(&cond); Pthread_mutex_unlock(&mutex);

A mutex is always associated with a condition variable. Guarantees that the condition cannot be signaled (and thus ignored) in the interval when the waiter locks the mutex and waits on the condition.

Basic pattern for waiting


Pthread_mutex_lock(&mutex); Pthread_cond_wait(&cond, &mutex); Pthread_mutex_unlock(&mutex);

class23.ppt

28

CS 213 F00

#include <ics.h> static static static static pthread_mutex_t mutex; pthread_cond_t cond; int nthreads; int barriercnt = 0;

Barrier synchronization
Call to barrier will not return until every other thread has also called barrier. Needed for tightlycoupled parallel applications that proceed in phases. E.g., physical simulations.

void barrier_init(int n) { nthreads = n; Pthread_mutex_init(&mutex, NULL); Pthread_cond_init(&cond, NULL); } void barrier() { Pthread_mutex_lock(&mutex); if (++barriercnt == nthreads) { barriercnt = 0; Pthread_cond_broadcast(&cond); } else Pthread_cond_wait(&cond, &mutex); Pthread_mutex_unlock(&mutex); }

class23.ppt

29

CS 213 F00

timebomb.c: timeout waiting example


A program that explodes unless the user hits a key within 5 seconds.
#include <ics.h> #define TIMEOUT 5 /* function prototypes */ void *thread(void *vargp); struct timespec *maketimeout(int secs); /* condition variable and its associated mutex */ pthread_cond_t cond; pthread_mutex_t mutex; /* thread id */ pthread_t tid;

class23.ppt

30

CS 213 F00

timebomb.c (cont)
A routine for building a timeout structure for pthread_cond_timewait.
/* * maketimeout - builds a timeout object that times out * in secs seconds */ struct timespec *maketimeout(int secs) { struct timeval now; struct timespec *tp = (struct timespec *)malloc(sizeof(struct timespec)); gettimeofday(&now, NULL); tp->tv_sec = now.tv_sec + secs; tp->tv_nsec = now.tv_usec * 1000; return tp; }

class23.ppt

31

CS 213 F00

Main routine for timebomb.c


int main() { int i, rc; /* initialize the mutex and condition variable */ Pthread_cond_init(&cond, NULL); Pthread_mutex_init(&mutex, NULL); /* start getchar thread and wait for it to timeout */ Pthread_mutex_lock(&mutex); Pthread_create(&tid, NULL, thread, NULL); for (i=0; i<TIMEOUT; i++) { printf("BEEP\n"); rc = pthread_cond_timedwait(&cond, &mutex, maketimeout(1)); if (rc != ETIMEDOUT) { printf("WHEW!\n"); exit(0); } } printf("BOOM!\n"); exit(0); }
class23.ppt 32 CS 213 F00

Thread routine for timebomb.c


/* * thread - executes getchar in a separate thread */ void *thread(void *vargp) { (void) getchar(); Pthread_mutex_lock(&mutex); Pthread_cond_signal(&cond); Pthread_mutex_unlock(&mutex); return NULL; }

class23.ppt

33

CS 213 F00

Threads summary Threads provide another mechanism for writing


concurrent programs. Threads are growing in popularity
Somewhat cheaper than processes. Easy to share data between threads.

However, the ease of sharing has a cost:


Easy to introduce subtle synchronization errors.

For more info:


man pages (man -k pthreads) D. Butenhof, Programming with Posix Threads, Addison-Wesley, 1997.
class23.ppt 34 CS 213 F00

You might also like