Synchronization /18-243: Introduction to Computer Systems

Slides:



Advertisements
Similar presentations
Copyright © 2000, Daniel W. Lewis. All Rights Reserved. CHAPTER 10 SHARED MEMORY.
Advertisements

Programming with Threads Topics Threads Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy Races.
Carnegie Mellon 1 Synchronization: Advanced / : Introduction to Computer Systems 25 th Lecture, April 21, 2015 Instructors: Seth Copen Goldstein,
SYNCHRONIZATION-2 Slides from: Computer Systems: A Programmer's Perspective, 2nd Edition by Randal E. Bryant and David R. O'Hallaron, Prentice Hall, 2011.
Carnegie Mellon 1 Synchronization: Advanced : Introduction to Computer Systems 24 th Lecture, Nov. 18, 2010 Instructors: Randy Bryant and Dave O’Hallaron.
CONDITION VARIABLE. Announcements  Quiz  Getting the big picture  Programming assignments  Cooperation  Lecture is cut 20 mins short for Quiz and.
Programming with Threads November 26, 2003 Topics Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy.
Carnegie Mellon 1 Synchronization: Basics : Introduction to Computer Systems 23 rd Lecture, Nov. 16, 2010 Instructors: Randy Bryant and Dave O’Hallaron.
– 1 – , F’02 Traditional View of a Process Process = process context + code, data, and stack shared libraries run-time heap 0 read/write data Program.
Threads© Dr. Ayman Abdel-Hamid, CS4254 Spring CS4254 Computer Network Architecture and Programming Dr. Ayman A. Abdel-Hamid Computer Science Department.
Synchronization April 29, 2008 Topics Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy Races and.
Concurrent HTTP Proxy with Caching Ashwin Bharambe Monday, Dec 4, 2006.
Programming with Threads Topics Threads Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy Races.
Carnegie Mellon 1 Synchronization: Basics / : Introduction to Computer Systems 23 rd Lecture, Jul 21, 2015 Instructors: nwf and Greg Kesden.
Programming with Threads April 30, 2002 Topics Shared variables The need for synchronization Synchronizing with semaphores Synchronizing with mutex and.
Carnegie Mellon /18-243: Introduction to Computer Systems Instructors: Bill Nace and Gregory Kesden (c) All Rights Reserved. All work.
1 Process Synchronization Andrew Case Slides adapted from Mohamed Zahran, Clark Barrett, Jinyang Li, Randy Bryant and Dave O’Hallaron.
1 Concurrent Programming. 2 Outline Semaphores for Shared Resources –Producer-consumer problem –Readers-writers problem Example –Concurrent server based.
1. Synchronization: basic 2. Synchronization: advanced.
SYNCHRONIZATION-2 Slides from: Computer Systems: A Programmer's Perspective, 2nd Edition by Randal E. Bryant and David R. O'Hallaron, Prentice Hall, 2011.
Week 16 (April 25 th ) Outline Thread Synchronization Lab 7: part 2 & 3 TA evaluation form Reminders Lab 7: due this Thursday Final review session Final.
Threads Chapter 26. Threads Light-weight processes Each process can have multiple threads of concurrent control. What’s wrong with processes? fork() is.
Synchronizing Threads with Semaphores
Carnegie Mellon 1 Synchronization: Advanced / : Introduction to Computer Systems 24 th Lecture, Nov. 18, 2014 Instructors: Greg Ganger, Greg.
SYNCHRONIZATION. Today  Threads review  Sharing  Mutual exclusion  Semaphores.
Carnegie Mellon 1 Synchronization: Advanced / : Introduction to Computer Systems 25 th Lecture, July 28, 2014 Instructors: Greg Kesden.
Carnegie Mellon Introduction to Computer Systems /18-243, fall nd Lecture, Nov. 17 Instructors: Roger B. Dannenberg and Greg Ganger.
Programming with Threads Topics Threads Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy Races.
Thread S04, Recitation, Section A Thread Memory Model Thread Interfaces (System Calls) Thread Safety (Pitfalls of Using Thread) Racing Semaphore.
Concurrency I: Threads Nov 9, 2000 Topics Thread concept Posix threads (Pthreads) interface Linux Pthreads implementation Concurrent execution Sharing.
1 Carnegie Mellon Synchronization : Introduction to Computer Systems Recitation 14: November 25, 2013 Pratik Shah (pcshah) Section C.
1. Concurrency and threads 2. Synchronization: basic 3. Synchronization: advanced.
Concurrency Threads Synchronization Thread-safety of Library Functions Anubhav Gupta Dec. 2, 2002 Outline.
1 Introduction to Threads Race Conditions. 2 Process Address Space Revisited Code Data OS Stack (a)Process with Single Thread (b) Process with Two Threads.
Carnegie Mellon 1 Synchronization: Advanced / : Introduction to Computer Systems 25 th Lecture, Nov. 29, 2011 Instructors: Dave O’Hallaron,
1 CMSC Laundry List P/W/F, Exam, etc. Instructors: HSG, HH, MW.
December 1, 2006©2006 Craig Zilles1 Threads & Atomic Operations in Hardware  Previously, we introduced multi-core parallelism & cache coherence —Today.
Instructors: Dave O’Hallaron, Greg Ganger, and Greg Kesden
Synchronization: Advanced / : Introduction to Computer Systems 25th Lecture, April 19, 2014 Instructors: Seth Copen Goldstein, Franz Franchetti,
Instructors: Gregory Kesden and Markus Püschel
Threads Synchronization Thread-safety of Library Functions
Synchronization: Basics
Instructors: Randal E. Bryant and David R. O’Hallaron
Programming with Threads
Programming with Threads
Threads Threads.
Concurrent Programming November 13, 2008
Instructor: Randy Bryant
Concurrency I: Threads April 10, 2001
Instructor: Randy Bryant
Programming with Threads
CS703 – Advanced Operating Systems
Concurrent Programming
Programming with Threads
Programming with Threads Dec 6, 2001
Programming with Threads Dec 5, 2002
CS703 - Advanced Operating Systems
Concurrent Programming November 18, 2008
Synchronization: Basics CSCI 380: Operating Systems
Programming with Threads
Programming with Threads
Synchronization: Advanced CSCI 380: Operating Systems
Concurrent Programming November 13, 2008
Programming with Threads
Lecture 20: Synchronization
Instructor: Brian Railing
Lecture 19: Threads CS April 3, 2019.
Programming with Threads
Threads CSE 2431: Introduction to Operating Systems
Presentation transcript:

Synchronization 15-213/18-243: Introduction to Computer Systems Carnegie Mellon Synchronization 15-213/18-243: Introduction to Computer Systems 26th Lecture, 27 April 2010 Instructors: Bill Nace and Gregory Kesden (c) 1998 - 2010. All Rights Reserved. All work contained herein is copyrighted and used by permission of the authors. Contact 15-213-staff@cs.cmu.edu for permission or for more information.

Last Time: Process-based Server Carnegie Mellon Last Time: Process-based Server Connection Requests Listening Server Client 1 Server Process listenfd Client 1 data Client 2 Server Process connfd Client 2 data connfd Each client handled by independent process No shared state between them When child created, each has copy of listenfd and connfd Parent must close connfd, child must close listenfd

Last Time: A Process With Multiple Threads Carnegie Mellon Last Time: A Process With Multiple Threads Multiple threads can be associated with a process Each thread has its own logical control flow Each thread shares the same code, data, and kernel context Share common virtual address space Each thread has its own thread id (TID) Thread 1 (main thread) Thread 2 (peer thread) shared libraries run-time heap read/write data read-only code/data brk stack 1 stack 2 SP SP PC Thread 1 context: Data registers Condition codes SP1 PC1 Thread 2 context: Data registers Condition codes SP2 PC2 Kernel context: VM structures Descriptor table brk pointer

Carnegie Mellon Today Synchronization Races, deadlocks, thread safety

Shared Variables in Threaded C Programs Carnegie Mellon Shared Variables in Threaded C Programs Question: Which variables in a threaded C program are shared variables? The answer is not as simple as “global variables are shared” and “stack variables are private” Requires answers to the following questions: What is the memory model for threads? How are variables mapped to each memory instance? How many threads might reference each of these instances?

Threads Memory Model Conceptual model: Carnegie Mellon Threads Memory Model Conceptual model: Multiple threads run within the context of a single process Each thread has its own separate thread context Thread ID, stack, stack pointer, program counter, condition codes, and general purpose registers All threads share the remaining process context Code, data, heap, and shared library segments Open files and installed handlers Operationally, this model is not strictly enforced: Register values are truly separate and protected, but Any thread can read and write the stack of any other thread Mismatch between the conceptual and operation model is a source of confusion and errors

Thread Accessing Another Thread’s Stack Carnegie Mellon Thread Accessing Another Thread’s Stack char **ptr; /* global */ int main() { int i; pthread_t tid; char *msgs[2] = { "Hello from foo", "Hello from bar" }; ptr = msgs; for (i = 0; i < 2; i++) Pthread_create(&tid, NULL, thread, (void *)i); Pthread_exit(NULL); } /* thread routine */ void *thread(void *vargp) { int myid = (int) vargp; static int svar = 0; printf("[%d]: %s (svar=%d)\n", myid, ptr[myid], ++svar); } Peer threads access main thread’s stack indirectly through global ptr variable

Mapping Variables to Memory Instances Carnegie Mellon Mapping Variables to Memory Instances Global var: 1 instance (ptr [data]) Local vars: 1 instance (i.m, msgs.m) char **ptr; /* global */ int main() { int i; pthread_t tid; char *msgs[2] = { "Hello from foo", "Hello from bar" }; ptr = msgs; for (i = 0; i < 2; i++) Pthread_create(&tid, NULL, thread, (void *)i); Pthread_exit(NULL); } Local var: 2 instances myid.p0 [peer thread 0’s stack], myid.p1 [peer thread 1’s stack /* thread routine */ void *thread(void *vargp) { int myid = (int)vargp; static int svar = 0; printf("[%d]: %s (svar=%d)\n", myid, ptr[myid], ++svar); } Local static var: 1 instance (svar [data])

Shared Variable Analysis Carnegie Mellon Shared Variable Analysis Which variables are shared? Answer: A variable x is shared iff multiple threads reference at least one instance of x. Thus: ptr, svar, and msgs are shared i and myid are not shared Variable Referenced by Referenced by Referenced by instance main thread? peer thread 0? peer thread 1? ptr yes yes yes svar no yes yes i.m yes no no msgs.m yes yes yes myid.p0 no yes no myid.p1 no no yes

badcnt.c: Improper Synchronization Carnegie Mellon badcnt.c: Improper Synchronization /* shared */ volatile unsigned int cnt = 0; #define NITERS 100000000 int main() { pthread_t tid1, tid2; Pthread_create(&tid1, NULL, count, NULL); Pthread_create(&tid2, NULL, Pthread_join(tid1, NULL); Pthread_join(tid2, NULL); if (cnt != (unsigned)NITERS*2) printf("BOOM! cnt=%d\n", cnt); else printf("OK cnt=%d\n", cnt); } /* thread routine */ void *count(void *arg) { int i; for (i=0; i<NITERS; i++) cnt++; return NULL; } linux> ./badcnt BOOM! cnt=198841183 BOOM! cnt=198261801 BOOM! cnt=198269672 cnt should be equal to 200,000,000 What went wrong?

Assembly Code for Counter Loop Carnegie Mellon Assembly Code for Counter Loop C code for counter loop in thread i for (i=0; i<NITERS; i++) cnt++; Corresponding assembly code .L9: movl -4(%ebp),%eax cmpl $99999999,%eax jle .L12 jmp .L10 .L12: movl cnt,%eax # Load leal 1(%eax),%edx # Update movl %edx,cnt # Store .L11: leal 1(%eax),%edx movl %edx,-4(%ebp) jmp .L9 .L10: Head (Hi) Load cnt (Li) Update cnt (Ui) Store cnt (Si) Tail (Ti)

Carnegie Mellon Concurrent Execution Key idea: In general, any sequentially consistent interleaving is possible, but some give an unexpected result! Ii denotes that thread i executes instruction I %eaxi is the content of %eax in thread i’s context i (thread) instri %eaxi %eax2 cnt 1 H1 - L1 U1 S1 2 H2 L2 U2 S2 T2 T1 Time OK

Concurrent Execution (cont) Carnegie Mellon Concurrent Execution (cont) Incorrect ordering: two threads increment the counter, but the result is 1 instead of 2 i (thread) instri %eaxi %eax2 cnt 1 H1 - L1 U1 2 H2 L2 S1 T1 U2 S2 T2 Time Oops!

Concurrent Execution (cont) Carnegie Mellon Concurrent Execution (cont) How about this ordering? We can analyze the behavior using a process graph i (thread) instri %eaxi %eax2 cnt 1 H1 - L1 2 H2 L2 U2 S2 U1 S1 T1 T2 Time

Progress Graphs (L1, S2) Thread 2 T2 S2 U2 L2 H2 H1 L1 U1 S1 T1 Carnegie Mellon Progress Graphs 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 Thread 2 T2 (L1, S2) S2 U2 L2 H2 H1 L1 U1 S1 T1 Thread 1

Trajectories in Progress Graphs Carnegie Mellon Trajectories in Progress Graphs H1 L1 U1 S1 T1 H2 L2 U2 S2 T2 Thread 1 Thread 2 A trajectory is a sequence of legal state transitions that describes one possible concurrent execution of the threads. Example: H1, L1, U1, H2, L2, S1, T1, U2, S2, T2

Critical Sections and Unsafe Regions Carnegie Mellon Critical Sections and Unsafe Regions Thread 2 L, U, and S form a critical section with respect to the shared variable cnt Instructions in critical sections (WRT to some shared variable) should not be interleaved Sets of states where such interleaving occurs form unsafe regions T2 S2 critical section wrt cnt U2 Unsafe region L2 H2 Thread 1 H1 L1 U1 S1 T1 critical section wrt cnt

Critical Sections and Unsafe Regions Carnegie Mellon Critical Sections and Unsafe Regions Thread 2 safe T2 Definition: A trajectory is safe iff it does not enter any unsafe region Claim: A trajectory is correct (wrt cnt) iff it is safe S2 critical section wrt cnt U2 Unsafe region L2 unsafe H2 Thread 1 H1 L1 U1 S1 T1 critical section wrt cnt

Semaphores Question: How can we guarantee a safe trajectory? Carnegie Mellon Semaphores 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 global 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)

badcnt.c: Improper Synchronization Carnegie Mellon badcnt.c: Improper Synchronization /* shared */ volatile unsigned int cnt = 0; #define NITERS 100000000 int main() { pthread_t tid1, tid2; Pthread_create(&tid1, NULL, count, NULL); Pthread_create(&tid2, NULL, Pthread_join(tid1, NULL); Pthread_join(tid2, NULL); if (cnt != (unsigned)NITERS*2) printf("BOOM! cnt=%d\n", cnt); else printf("OK cnt=%d\n", cnt); } /* thread routine */ void *count(void *arg) { int i; for (i=0; i<NITERS; i++) cnt++; return NULL; } How to fix using semaphores?

Safe Sharing with Semaphores Carnegie Mellon Safe Sharing with Semaphores One semaphore per shared variable Initially set to 1 Here is how we would use P and V operations to synchronize the threads that update cnt /* Semaphore s is initially 1 */ /* Thread routine */ void *count(void *arg) { int i; for (i=0; i<NITERS; i++) { P(s); cnt++; V(s); } return NULL;

Safe Sharing With Semaphores Carnegie Mellon Safe Sharing With Semaphores Thread 2 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 entered by any trajectory 1 1 1 1 T2 1 1 1 1 V(s) Forbidden region -1 -1 -1 -1 S2 -1 -1 -1 -1 U2 Unsafe region -1 -1 -1 -1 L2 -1 -1 -1 -1 P(s) 1 1 1 1 H2 1 1 1 1 Thread 1 H1 P(s) L1 U1 S1 V(s) T1 Initially s = 1

Wrappers on POSIX Semaphores Carnegie Mellon Wrappers on 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");

Sharing With POSIX Semaphores Carnegie Mellon Sharing With POSIX Semaphores /* properly sync’d counter program */ #include "csapp.h" #define NITERS 10000000 volatile unsigned int cnt; sem_t sem; /* semaphore */ int main() { pthread_t tid1, tid2; Sem_init(&sem, 0, 1); /* sem=1 */ /* create 2 threads and wait */ ... if (cnt != (unsigned)NITERS*2) printf("BOOM! cnt=%d\n", cnt); else printf("OK cnt=%d\n", cnt); exit(0); } /* thread routine */ void *count(void *arg) { int i; for (i=0; i<NITERS; i++) { P(&sem); cnt++; V(&sem); } return NULL; linux> ./goodcnt 100000000 OK cnt=200000000 Warning: Extremely slow!

Notifying With Semaphores Carnegie Mellon Notifying With Semaphores producer thread shared buffer consumer thread Common synchronization pattern: Producer waits for slot, inserts item in buffer, and notifies consumer Consumer waits for item, removes it from buffer, and notifies producer Examples Multimedia processing: Producer creates MPEG video frames, consumer renders them Event-driven 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

Producer-Consumer on a Buffer That Holds One Item Carnegie Mellon Producer-Consumer on a Buffer That Holds One Item /* buf1.c - producer-consumer on 1-element buffer */ #include “csapp.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); }

Producer-Consumer (cont) Carnegie Mellon 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; /* 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;

Counting with Semaphores Carnegie Mellon Counting with Semaphores Remember, it’s a non-negative integer So, values greater than 1 are legal Lets repeat thing_5() 5 times for every 3 of thing_3() /* thing_5 and thing_3 */ #include “csapp.h” sem_t five; sem_t three; void *five_times(void *arg); void *three_times(void *arg); int main() { pthread_t tid_five, tid_three; /* initialize the semaphores */ Sem_init(&five, 0, 5); Sem_init(&three, 0, 3); /* create threads and wait */ Pthread_create(&tid_five, NULL, five_times, NULL); Pthread_create(&tid_three, NULL, three_times, NULL); . }

Counting with Semaphores (cont) Carnegie Mellon Counting with Semaphores (cont) Initially: five = 5, three = 3 /* thing_5() thread */ void *five_times(void *arg) { int i; while (1) { for (i=0; i<5; i++) { /* wait & thing_5() */ P(&five); thing_5(); } V(&three); return NULL; /* thing_3() thread */ void *three_times(void *arg) { int i; while (1) { for (i=0; i<3; i++) { /* wait & thing_3() */ P(&three); thing_3(); } V(&five); return NULL;

Carnegie Mellon Today Synchronization Races, deadlocks, thread safety

Carnegie Mellon One worry: races A race occurs when correctness of the program depends on a thread reaching point x before another thread reaches point y /* a threaded program with a race */ int main() { pthread_t tid[N]; int i; for (i = 0; i < N; i++) Pthread_create(&tid[i], NULL, thread, &i); Pthread_join(tid[i], NULL); exit(0); } /* thread routine */ void *thread(void *vargp) { int myid = *((int *)vargp); printf("Hello from thread %d\n", myid); return NULL; linux> ./race Hello from thread 1 Hello from thread 3 Hello from thread 2

Race Elimination Make sure there is no unintended sharing of state Carnegie Mellon Race Elimination Make sure there is no unintended sharing of state /* a threaded program with a race removed*/ int main() { pthread_t tid[N]; int i; for (i = 0; i < N; i++) { int *valp = malloc(sizeof(int)); *valp = i; Pthread_create(&tid[i], NULL, thread, valp); for (i = 0; i < N; i++) Pthread_join(tid[i], NULL); exit(0); } /* thread routine */ void *thread(void *vargp) { int myid = *((int *)vargp); free(vargp); printf("Hello from thread %d\n", myid); return NULL;

Another worry: Deadlock Carnegie Mellon Another worry: Deadlock Processes wait for condition that will never be true Typical Scenario Processes 1 and 2 needs two resources (A and B) to proceed Process 1 acquires A, waits for B Process 2 acquires B, waits for A Both will wait forever!

Deadlocking With POSIX Semaphores Carnegie Mellon Deadlocking With POSIX Semaphores int main() { pthread_t tid[2]; Sem_init(&mutex[0], 0, 1); /* mutex[0] = 1 */ Sem_init(&mutex[1], 0, 1); /* mutex[1] = 1 */ Pthread_create(&tid[0], NULL, count, (void*) 0); Pthread_create(&tid[1], NULL, count, (void*) 1); Pthread_join(tid[0], NULL); Pthread_join(tid[1], NULL); printf("cnt=%d\n", cnt); exit(0); } void *count(void *vargp) { int i; int id = (int) vargp; for (i = 0; i < NITERS; i++) { P(&mutex[id]); P(&mutex[1-id]); cnt++; V(&mutex[id]); V(&mutex[1-id]); } return NULL; Tid[0]: P(s0); P(s1); cnt++; V(s0); V(s1); Tid[1]: P(s1); P(s0); cnt++; V(s1); V(s0);

Deadlock Visualized in Progress Graph Carnegie Mellon Deadlock Visualized in Progress Graph Thread 2 Locking introduces 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 s0 or s1 to become nonzero Other trajectories luck out and skirt the deadlock region Unfortunate fact: deadlock is often non-deterministic V(s0) Forbidden region for s1 V(s1) P(s0) Forbidden region for s2 deadlock region P(s1) deadlock state Thread 1 P(s0) P(s1) V(s0) V(s1) s0=s1=1

Avoiding Deadlock Acquire shared resources in the same order Tid[0]: Carnegie Mellon Avoiding Deadlock Acquire shared resources in the same order int main() { pthread_t tid[2]; Sem_init(&mutex[0], 0, 1); /* mutex[0] = 1 */ Sem_init(&mutex[1], 0, 1); /* mutex[1] = 1 */ Pthread_create(&tid[0], NULL, count, (void*) 0); Pthread_create(&tid[1], NULL, count, (void*) 1); Pthread_join(tid[0], NULL); Pthread_join(tid[1], NULL); printf("cnt=%d\n", cnt); exit(0); } void *count(void *vargp) { int i; int id = (int) vargp; for (i = 0; i < NITERS; i++) { P(&mutex[0]); P(&mutex[1]); cnt++; V(&mutex[id]); V(&mutex[1-id]); } return NULL; Tid[0]: P(s0); P(s1); cnt++; V(s0); V(s1); Tid[1]: P(s0); P(s1); cnt++; V(s1); V(s0);

Avoided Deadlock in Progress Graph Carnegie Mellon Avoided Deadlock in Progress Graph Thread 2 No way for trajectory to enter a deadlock region Threads acquire locks in the same order Order in which locks are released is immaterial V(s0) Forbidden region for s1 V(s1) P(s0) Forbidden region for s2 P(s1) Thread 1 P(s0) P(s1) V(s0) V(s1) s0=s1=1

Crucial concept: Thread Safety Carnegie Mellon Crucial concept: Thread Safety Functions called from a thread (without external synchronization) must be thread-safe Meaning: it must always produce correct results when called repeatedly from multiple concurrent threads Some examples of thread-unsafe activities: Failing to protect shared variables Relying on persistent state across invocations Returning a pointer to a static variable Calling a thread-unsafe functions

Thread-Unsafe Functions (Class 1) Carnegie Mellon Thread-Unsafe Functions (Class 1) Failing to protect shared variables Fix: Use P and V semaphore operations Example: goodcnt.c Issue: Synchronization operations will slow down code e.g., badcnt requires 0.5s, goodcnt requires 7.9s

Thread-Unsafe Functions (Class 2) Carnegie Mellon Thread-Unsafe Functions (Class 2) Relying on persistent state across multiple function invocations Example: Random number generator (RNG) that relies on static state /* rand: return pseudo-random integer on 0..32767 */ static unsigned int next = 1; int rand(void) { next = next*1103515245 + 12345; return (unsigned int)(next/65536) % 32768; } /* srand: set seed for rand() */ void srand(unsigned int seed) next = seed;

Making Thread-Safe RNG Carnegie Mellon Making Thread-Safe RNG Pass state as part of argument and, thereby, eliminate static state Consequence: programmer using rand_r must maintain seed /* rand - return pseudo-random integer on 0..32767 */ int rand_r(int *nextp) { *nextp = *nextp*1103515245 + 12345; return (unsigned int)(*nextp/65536) % 32768; }

Thread-Unsafe Functions (Class 3) Carnegie Mellon Thread-Unsafe Functions (Class 3) struct hostent *gethostbyname(char name) { static struct hostent h; <contact DNS and fill in h> return &h; } Returning a ptr to a static variable Fixes: 1. Rewrite code so caller passes pointer to struct Issue: Requires changes in caller and callee 2. Lock-and-copy Issue: Requires only simple changes in caller (and none in callee) However, caller must free memory hostp = Malloc(...); gethostbyname_r(name, hostp); struct hostent *gethostbyname_ts(char *name) { struct hostent *q = Malloc(...); struct hostent *p; P(&mutex); /* lock */ p = gethostbyname(name); *q = *p; /* copy */ V(&mutex); return q; }

Thread-Unsafe Functions (Class 4) Carnegie Mellon Thread-Unsafe Functions (Class 4) Calling thread-unsafe functions Calling one thread-unsafe function makes the entire function that calls it thread-unsafe Fix: Modify the function so it calls only thread-safe functions ☺

Thread-Safe Library Functions Carnegie Mellon Thread-Safe Library Functions All functions in the Standard C Library (at the back of your K&R text) are thread-safe Examples: malloc, free, printf, scanf Most Unix system calls are thread-safe, with a few exceptions: Thread-unsafe function Class Reentrant version asctime 3 asctime_r ctime 3 ctime_r gethostbyaddr 3 gethostbyaddr_r gethostbyname 3 gethostbyname_r inet_ntoa 3 (none) localtime 3 localtime_r rand 2 rand_r

Carnegie Mellon 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 Which are very, very, very, very, very difficult to discover Tread carefully with threads! For more info: D. Butenhof, “Programming with Posix Threads”, Addison-Wesley, 1997

Beware of Optimizing Compilers! Carnegie Mellon Code From Book Generated Code #define NITERS 100000000 /* shared counter variable */ unsigned int cnt = 0; /* thread routine */ void *count(void *arg) { int i; for (i = 0; i < NITERS; i++) cnt++; return NULL; } movl cnt, %ecx movl $99999999, %eax .L6: leal 1(%ecx), %edx decl %eax movl %edx, %ecx jns .L6 movl %edx, cnt Compiler moved access to cnt out of loop Only shared accesses to cnt occur before loop (read) or after (write) What are possible program outcomes? Global variable cnt shared between threads Multiple threads could be trying to update within their iterations

Controlling Optimizing Compilers! Carnegie Mellon Revised Book Code Generated Code #define NITERS 100000000 /* shared counter variable */ volatile unsigned int cnt = 0; /* thread routine */ void *count(void *arg) { int i; for (i = 0; i < NITERS; i++) cnt++; return NULL; } movl $99999999, %edx .L15: movl cnt, %eax incl %eax decl %edx movl %eax, cnt jns .L15 Declaring variable as volatile forces it to be kept in memory Shared variable read and written each iteration

Summary Synchronization Thread Safety Next Time: Shared variables Carnegie Mellon Summary Synchronization Shared variables Process graphs Thread Safety Deadlocks, semaphores Next Time: Multi-core Architectures