Carnegie Mellon
Synchronization: Basics
15-213: Introduction to Computer Systems 25th Lecture, April 16, 2020
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 1
Carnegie Mellon
Today
Threads review Sharing
Mutual exclusion Semaphores
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 2
Carnegie Mellon
Traditional View of a Process
Process = process context + code, data, and stack
Process context
Code, data, and stack
SP
brk
PC
0
Stack
Shared libraries
Run-time heap
Read/write data
Read-only code/data
Program context:
Data registers Condition codes Stack pointer (SP) Program counter (PC)
Kernel context: VM structures
Descriptor table brk pointer
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
3
Carnegie Mellon
Alternate View of a Process
Process = thread + (code, data, and kernel context)
Thread (main thread) Code, data, and kernel context
brk
PC
0
Shared libraries
Run-time heap
Read/write data
Read-only code/data
SP
Stack
Thread context: Data registers
Condition codes Stack pointer (SP) Program counter (PC)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
4
Kernel context: VM structures
Descriptor table brk pointer
Carnegie Mellon
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 Each thread has its own stack for local variables
but not protected from other threads Each thread has its own thread id (TID)
Thread 1 (main thread) Thread 2 (peer thread)
stack 1 stack 2
Shared code and data
shared libraries
run-time heap
read/write data
read-only code/data
Thread 1 context: Data registers Condition codes SP1
PC1
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
5
Thread 2 context: Data registers Condition codes SP2
PC2
0
Kernel context: VM structures
Descriptor table brk pointer
Don’t let picture confuse you!
Thread 1 (main thread) Thread 2 (peer thread)
stack 1 stack 2
Shared code and data
Carnegie Mellon
shared libraries
run-time heap
read/write data
read-only code/data
Thread 1 context: Data registers Condition codes SP1
PC1
Thread 2 context: Data registers Condition codes SP2
PC2
Memory is shared between all threads
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
0
Kernel context: VM structures
Descriptor table brk pointer
6
Carnegie Mellon
Today
Threads review
Sharing
Mutual exclusion
Semaphores
Producer-Consumer Synchronization
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 7
Carnegie Mellon
Shared Variables in Threaded C Programs
Question: Which variables in a threaded C program are shared?
The answer is not as simple as “global variables are shared” and “stack variables are private”
Def: A variable x is shared if and only if multiple threads reference some instance of x.
Requires answers to the following questions: What is the memory model for threads?
How are instances of variables mapped to memory?
How many threads might reference each of these instances?
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 8
Carnegie Mellon
Threads Memory Model: Conceptual
Multiple threads run within the context of a single process
Each thread has its own separate thread context Thread ID, stack, stack pointer, PC, condition codes, and GP registers
All threads share the remaining process context
Code, data, heap, and shared library segments of the process virtual address space
Open files and installed handlers
Thread 1 (private) stack 1
Thread 2 (private) stack 2
shared libraries
run-time heap
read/write data
read-only code/data
Thread 1 context: Data registers Condition codes SP1
PC1
Thread 2 context: Data registers Condition codes SP2
PC2
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 9
Shared code and data
Carnegie Mellon
Threads Memory Model: Actual
Separation of data is not strictly enforced:
Register values are truly separate and protected, but…
Any thread can read and write the stack of any other thread
stack 1
Virtual Address Space
Shared code and data
stack 2
Thread 1 (private)
Thread 2 (private)
shared libraries
run-time heap
read/write data
read-only code/data
Thread 1 context: Data registers Condition codes SP1
PC1
Thread 2 context: Data registers Condition codes SP2
PC2
The mismatch between the conceptual and operation model
is a source of confusion and errors
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 10
Carnegie Mellon
Passing an argument to a thread – Pedantic
int hist[N] = {0};
int main(int argc, char *argv[]) {
long i;
pthread_t tids[N];
for (i = 0; i < N; i++) {
long* p = Malloc(sizeof(long));
*p = i;
Pthread_create(&tids[i],
NULL, thread, (void *)p);
}
for (i = 0; i < N; i++)
Pthread_join(tids[i], NULL);
check();
}
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 11
void *thread(void *vargp)
{
hist[*(long *)vargp] += 1;
Free(vargp);
return NULL;
}
void check(void) {
for (int i=0; i
OK cnt=20000
linux> ./badcnt 10000
BOOM! cnt=13051
linux>
badcnt.c: Improper Synchronization
cnt should equal 20,000.
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition What went wrong? 27
Carnegie Mellon
Assembly Code for Counter Loop
C code for counter loop in thread i
for (i = 0; i < niters; i++)
cnt++;
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
28
Asm code for thread i
movq (%rdi), %rcx
testq %rcx,%rcx
jle .L2
movl $0, %eax
.L3:
movq cnt(%rip),%rdx
addq $1, %rdx
movq %rdx, cnt(%rip)
.L2:
addq $1, %rax
cmpq %rcx, %rax
jne .L3
Hi : Head
Li : Load cnt
Ui : Update cnt Si : Store cnt
Ti : Tail
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
%rdxi is the content of %rdx in thread i’s context
i (thread)
instri %rdx1 %rdx2 cnt
1
H1
-
-
0
1
L1
0
-
0
1
U1
1
-
0
1
S1
1
-
1
2
H2
-
-
1
2
L2
-
1
1
2
U2
-
2
1
2
S2
-
2
2
2
T2
-
2
2
1
T1
1
-
2
*For now. In reality, on x86 even non-sequentially consistent interleavings are possible
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 29
OK
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
%rdxi is the content of %rdx in thread i’s context
i (thread)
instri
%rdx1 %rdx2
cnt
1
H1
-
-
0
1
L1
0
-
0
1
U1
1
-
0
1
S1
1
-
1
2
H2
-
-
1
2
L2
-
1
1
2
U2
-
2
1
2
S2
-
2
2
2
T2
-
2
2
1
T1
1
-
2
Thread 1
critical section
Thread 2
critical section
OK
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
30
Carnegie Mellon
Concurrent Execution (cont)
Incorrect ordering: two threads increment the counter, but the result is 1 instead of 2
i (thread)
instri %rdx1 %rdx2 cnt
1
H1
-
-
0
1
L1
0
-
0
1
U1
1
-
0
2
H2
-
-
0
2
L2
-
0
0
1
S1
1
-
1
1
T1
1
-
1
2
U2
-
1
1
2
S2
-
1
1
2
T2
-
1
1
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
31
Oops!
Carnegie Mellon
Concurrent Execution (cont) How about this ordering?
i (thread)
instri %rdx1 %rdx2 cnt
1
H1
0
1
L1
0
2
H2
2
L2
0
2
U2
1
2
S2
1
1
1
U1
1
1
S1
1
1
1
T1
1
2
T2
1
We can analyze the behavior using a progress graph
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 32
Oops!
Carnegie Mellon
Progress Graphs
Thread 2
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.
T2 S2
U2
L2 H2
(L1, S2)
H1 L1 U1 S1 T1
Thread 1
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
33
Carnegie Mellon
Trajectories in Progress Graphs
Thread 2
T2 S2
U2
L2 H2
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
H1 L1 U1 S1 T1
Thread 1
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
34
Carnegie Mellon
Trajectories in Progress Graphs
Thread 2
T2 S2
U2
L2 H2
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
H1 L1 U1 S1 T1
Thread 1
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
35
Carnegie Mellon
Critical Sections and Unsafe Regions
Thread 2
T2
S2 critical
section wrt cnt
L2 H2
critical section wrt cnt Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
L, U, and S form a critical section with respect to the shared variable cnt
Instructions in critical sections (wrt some shared variable) should not be interleaved
Sets of states where such interleaving occurs form unsafe regions
Thread 1
U2
Unsafe region
H1 L1 U1 S1 T1
36
Carnegie Mellon
Critical Sections and Unsafe Regions
Thread 2
T2
S2 critical
section wrt cnt
L2 H2
critical section wrt cnt Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
safe
U2
Def: A trajectory is safe iff it does not enter any unsafe region
Claim: A trajectory is correct (wrt cnt) iff it is safe
unsafe
Unsafe region
H1 L1 U1 S1 T1
Thread 1
37
badcnt.c: Improper Synchronization
niters.m
main
thread1
Carnegie Mellon
/* Global shared variable */
volatile long cnt = 0; /* Counter */
int main(int argc, char **argv)
{
long niters;
pthread_t tid1, tid2;
niters = atoi(argv[1]); Pthread_create(&tid1, NULL,
thread, &niters);
Pthread_create(&tid2, NULL,
thread, &niters);
Pthread_join(tid1, NULL);
Pthread_join(tid2, NULL);
/* Check result */
if (cnt != (2 * niters))
printf("BOOM! cnt=%ld\n", cnt);
else
printf("OK cnt=%ld\n", cnt); exit(0);
cnt
tid1.m
i.1
i.2
}
badcnt.c
/* Thread routine */
void *thread(void *vargp)
{
long i, niters =
*((long *)vargp);
for (i = 0; i < niters; i++)
cnt++;
return NULL; Variable
}
niters.1
thread2
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
niters.2
38
badcnt.c: Improper Synchronization
/* Thread routine */
void *thread(void *vargp)
{
long i, niters =
*((long *)vargp);
for (i = 0; i < niters; i++)
cnt++;
return NULL; Variable
}
thread1
Carnegie Mellon
/* Global shared variable */
volatile long cnt = 0; /* Counter */
int main(int argc, char **argv)
{
long niters;
pthread_t tid1, tid2;
niters = atoi(argv[1]); Pthread_create(&tid1, NULL,
thread, &niters);
Pthread_create(&tid2, NULL,
thread, &niters);
Pthread_join(tid1, NULL);
Pthread_join(tid2, NULL);
/* Check result */
if (cnt != (2 * niters))
printf("BOOM! cnt=%ld\n", cnt);
else
printf("OK cnt=%ld\n", cnt); exit(0);
}
badcnt.c
cnt
tid1.m
i.1
main
yes*
yes
no
yes
no
thread2
yes
niters.m
yes
no
no
no
yes
no
no
i.2
no
yes
niters.1
no
yes
no
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
niters.2
no
no
yes 39
Carnegie Mellon
Break Time!
Check out:
Quiz: day 25: Synchronization Basic
https://canvas.cmu.edu/courses/31656
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 40
Carnegie Mellon
#include "csapp.h" #define N 2
void *thread(void *vargp); long *pointers[N];
int main(int argc, char *argv[]) { long i;
pthread_t tids[N];
Bonus Quiz Question 6:
for (i = 0; i < N; i++)
Pthread_create(&tids[i], NULL, thread, (void *) i);
sleep(1); // Sleep-#1 for (i = 0; i < N; i++)
printf("Thread id %u has local value %ld\n", (int) tids[i], *pointers[i]);
for (i = 0; i < N; i++) Pthread_join(tids[i], NULL);
return 0;
void *thread(void *vargp) { long myid = (long) vargp; pointers[myid] = &myid; sleep(2); // Sleep-2 return NULL;
}
If the statement labeled "Sleep #1" is kept, the main thread might have a segmentation fault when referencing "pointers“?
True? False?
}
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 41
Carnegie Mellon
Today
Threads review
Sharing
Mutual exclusion
Semaphores
Producer-Consumer Synchronization
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 43
Carnegie Mellon
Enforcing Mutual Exclusion
Question: How can we guarantee a safe trajectory?
Answer: We must synchronize the execution of the threads so that they can never have an unsafe trajectory.
i.e., need to guarantee mutually exclusive access for each critical section.
Classic solution:
Semaphores (Edsger Dijkstra)
Other approaches (out of our scope) Mutex and condition variables (Pthreads) Monitors (Java)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 44
Carnegie Mellon
Semaphores
Semaphore: non-negative global integer synchronization variable.
Manipulated by P and V operations.
P(s)
If s is nonzero, then decrement s by 1 and return immediately.
Test and decrement operations occur atomically (indivisibly)
If s is zero, then suspend thread until s becomes nonzero and the thread is restarted by a V operation.
After restarting, the P operation decrements s and returns control to the caller.
V(s):
Increment s by 1.
Increment operation occurs atomically
If there are any threads blocked in a P operation waiting for s to become non- zero, then restart exactly one of those threads, which then completes its P operation by decrementing s.
Semaphore invariant: (s >= 0)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 45
Carnegie Mellon
Semaphores
Semaphore: non-negative global integer synchronization variable
Manipulated by P and V operations:
P(s): [ while (s == 0) wait(); s–; ]
Dutch for “Proberen” (test) V(s): [ s++; ]
Dutch for “Verhogen” (increment)
OS kernel 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)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 46
Carnegie Mellon
C Semaphore Operations Pthreads functions:
#include
int sem_init(sem_t *s, 0, unsigned int val);} /* s = val */
int sem_wait(sem_t *s); /* P(s) */
int sem_post(sem_t *s); /* V(s) */
CS:APP wrapper functions:
#include “csapp.h”
void P(sem_t *s); /* Wrapper function for sem_wait */ void V(sem_t *s); /* Wrapper function for sem_post */
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 47
Carnegie Mellon
/* Global shared variable */
volatile long cnt = 0; /* Counter */
int main(int argc, char **argv)
{
long niters;
pthread_t tid1, tid2;
niters = atoi(argv[1]); Pthread_create(&tid1, NULL,
thread, &niters);
Pthread_create(&tid2, NULL,
thread, &niters);
Pthread_join(tid1, NULL);
Pthread_join(tid2, NULL);
/* Check result */
if (cnt != (2 * niters))
printf(“BOOM! cnt=%ld\n”, cnt);
else
printf(“OK cnt=%ld\n”, cnt); exit(0);
}
badcnt.c
/* Thread routine */
void *thread(void *vargp)
{
long i, niters =
*((long *)vargp);
for (i = 0; i < niters; i++)
cnt++;
return NULL;
}
badcnt.c: Improper Synchronization
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
48
How can we fix this using semaphores?
Carnegie Mellon
Using Semaphores for Mutual Exclusion Basic idea:
Associate a unique semaphore mutex, initially 1, with each shared variable (or related set of shared variables).
Surround corresponding critical sections with P(mutex) and V(mutex) operations.
Terminology:
Binary semaphore: semaphore whose value is always 0 or 1
Mutex: binary semaphore used for mutual exclusion
P operation: “locking” the mutex
V operation: “unlocking” or “releasing” the mutex “Holding” a mutex: locked and not yet unlocked.
Counting semaphore: used as a counter for set of available resources.
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 49
Carnegie Mellon
goodcnt.c: Proper Synchronization Define and initialize a mutex for the shared variable cnt:
volatile long cnt = 0; /* Counter */
sem_t mutex; /* Semaphore that protects cnt */
sem_init(&mutex, 0, 1); /* mutex = 1 */
Surround critical section with P and V:
for (i = 0; i < niters; i++) {
P(&mutex);
cnt++;
V(&mutex); }
goodcnt.c
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
50
linux> ./goodcnt 10000
OK cnt=20000
linux> ./goodcnt 10000
OK cnt=20000
linux>
Warning: It’s orders of magnitude slower than badcnt.c.
Carnegie Mellon
goodcnt.c: Proper Synchronization Define and initialize a mutex for the shared variable cnt:
volatile long cnt = 0; /* Counter */
sem_t mutex; /* Semaphore that protects cnt */
sem_init(&mutex, 0, 1); /* mutex = 1 */
Surround critical section with P and V:
for (i = 0; i < niters; i++) { P(&mutex);
cnt++;
V(&mutex);
}
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
51
Function
Time (ms) niters = 106
goodcnt.c
lbinaudxc>n.t/goodcngtoo1d00c0n0t OK cnt=20000
Warning: It’s orders of magnitude slower than badcnt.c.
12.0 450.0
linux> ./goodcnt 10000
OK cnt=20000
linux>
Slowdown
1.0
37.5
Carnegie Mellon
Why Mutexes Work
Thread 2
T2
V(s)
S2
U2 L2 P(s)
H2
H1 P(s) L1 U1 S1 V(s) T1 Initially
Provide mutually exclusive access to shared variable by surrounding critical section with P and V operations on semaphore s (initially set to 1)
Unsafe region
-1 0
1000
Thread 1
s= 1
52
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Carnegie Mellon
Why Mutexes Work
Thread 2
T2
V(s)
S2
U2 L2 P(s)
H2
H1 P(s) L1 U1 S1 V(s) T1 Initially
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 that cannot be entered by any trajectory.
0
s= 1
53
1000
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Unsafe region
-1
Thread 1
Carnegie Mellon
Why Mutexes Work
Thread 2
T2
V(s)
S2
U2 L2 P(s)
H2
H1 P(s) L1 U1 S1 V(s) T1 Initially
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 that cannot be entered by any trajectory.
Unsafe region
0
0
01
s= 1
54
1000
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Thread 1
Carnegie Mellon
Why Mutexes Work
Thread 2
11000011
T2
V(s)
S2
U2 L2 P(s)
H21 1 0 0 0 0 1 1 Initially H1 P(s) L1 U1 S1 V(s) T1
s= 1
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 that cannot be entered by any trajectory.
11000011
0 0
Forbidden region
0 0
-1 -1 -1 -1 -1 -1 -1 -1
Unsafe region
-1 -1 -1 -1 -1 -1 -1 -1
00 00
00 00
00 00
11000011
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
55
Thread 1
Carnegie Mellon
Binary Semaphores – For Mutual Exlusion
Mutex is special case of semaphore
Value either 0 or 1
Pthreads provides pthread_mutex_t
Operations: lock, unlock
Recommended over general semaphores when
appropriate
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 56
goodmcnt.c: Mutex Synchronization Define and initialize a mutex for the shared variable cnt:
for (i = 0; i < niters; i++) {
pthread_mutex_lock(&mutex);
cnt++;
pthread_mutex_unlock(&mutex);
}
Function
Time (ms) niters = 106
goodcnt.c
badcnt
12.0
linux> ./goodmcnt 10000
OK cnt=20000
linux> ./goodmcnt 10000
OK cnt=20000
linux>
goodcnt
450.0
37.5
Carnegie Mellon
volatile long cnt = 0; /* Counter */
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); // No special attributes
Surround critical section with lock and unlock:
goodmcnt
214.0
Slowdown 1.0
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
17.8
57
Carnegie Mellon
Today
Threads review
Sharing
Mutual exclusion
Semaphores
Producer-Consumer Synchronization
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 58
Carnegie Mellon
Using Semaphores to Coordinate Access to Shared Resources
Basic idea: Thread uses a semaphore operation to notify another thread that some condition has become true
Use counting semaphores to keep track of resource state. Use binary semaphores to notify other threads.
The Producer-Consumer Problem
Mediating interactions between processes that generate
information and that then make use of that information
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 59
Carnegie Mellon
Producer-Consumer Problem
producer consumer thread thread
Common synchronization pattern:
Producer waits for empty slot, inserts item in buffer, and notifies consumer Consumer waits for item, removes it from buffer, and notifies producer
Examples
Multimedia processing:
Producer creates 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
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 60
shared buffer
Carnegie Mellon
Producer-Consumer on 1-element Buffer
Maintain two semaphores: full + empty
full
0
empty
1
full
1
empty
0
empty buffer
full buffer
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 61
Carnegie Mellon
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(int argc, char** argv) {
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);
return 0; }
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 62
Carnegie Mellon
Producer-Consumer on 1-element Buffer
Initially: empty==1, full==0
Producer Thread Consumer Thread
void *producer(void *arg) {
int i, item;
for (i=0; i
error();
if (++rear >= n) rear = 0;
buf[rear] = v;
items++;
}
int remove()
{
if (items == 0) error();
if (++front >= n) front = 0;
int v = buf[front];
items–;
return v;
}
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 68
Carnegie Mellon
Producer-Consumer on an n-element Buffer P1 Between 0 and n elements C1
Pn Cm
Requires a mutex and two counting semaphores:
mutex: enforces mutually exclusive access to the buffer and counters slots: counts the available slots in the buffer
items: counts the available items in the buffer
Makes use of general semaphores Will range in value from 0 to n
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 69
Carnegie Mellon
sbuf Package-Declarations
#include “csapp.h”
typedef struct {
int *buf;
/* Buffer array */ /* Maximum number of slots */ /* buf[front+1 (mod n)] is first item */ /* buf[rear] is last item */ /* Protects accesses to buf */
int n;
int front;
int rear;
sem_t mutex;
sem_t slots; /* Counts available slots */
sem_t items; /* Counts available items */
} sbuf_t;
void sbuf_init(sbuf_t *sp, int n);
void sbuf_deinit(sbuf_t *sp);
void sbuf_insert(sbuf_t *sp, int item);
int sbuf_remove(sbuf_t *sp);
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
70
sbuf.h
Carnegie Mellon
sbuf Package-Implementation Initializing and deinitializing a shared buffer:
/* Create an empty, bounded, shared FIFO buffer with n slots */ void sbuf_init(sbuf_t *sp, int n)
{
sp->buf = Calloc(n, sizeof(int));
sp->n = n; /* Buffer holds max of n items */ sp->front = sp->rear = 0; /* Empty buffer iff front == rear */ Sem_init(&sp->mutex, 0, 1); /* Binary semaphore for locking */ Sem_init(&sp->slots, 0, n); /* Initially, buf has n empty slots */ Sem_init(&sp->items, 0, 0); /* Initially, buf has zero items */
}
/* Clean up buffer sp */
void sbuf_deinit(sbuf_t *sp)
{
Free(sp->buf);
}
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
71
sbuf.c
Carnegie Mellon
sbuf Package-Implementation Inserting an item into a shared buffer:
/* Insert item onto the rear of shared buffer sp */
void sbuf_insert(sbuf_t *sp, int item)
{
}
P(&sp->slots);
P(&sp->mutex);
if (++sp->rear >= sp->n)
sp->rear = 0; sp->buf[sp->rear] = item; V(&sp->mutex); V(&sp->items);
/* Wait for available slot */
/* Lock the buffer */
/* Increment index (mod n) */
/* Insert the item */ /* Unlock the buffer */ /* Announce available item */
sbuf.c
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 72
Carnegie Mellon
sbuf Package-Implementation Removing an item from a shared buffer:
/* Remove and return the first item from buffer sp */
int sbuf_remove(sbuf_t *sp)
{
int item;
P(&sp->items);
P(&sp->mutex);
if (++sp->front >= sp->n)
sp->front = 0;
item = sp->buf[sp->front];
V(&sp->mutex);
V(&sp->slots);
return item;
}
/* Wait for available item */
/* Lock the buffer */
/* Increment index (mod n) */
/* Remove the item */
/* Unlock the buffer */
/* Announce available slot */
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 73
sbuf.c
Carnegie Mellon
Demonstration
See program produce-consume.c in code directory
10-entry shared circular buffer
5 producers
Agent i generates numbers from 20*i to 20*i – 1. Puts them in buffer
5 consumers
Each retrieves 20 elements from buffer
Main program
Makes sure each value between 0 and 99 retrieved once
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 74
Carnegie Mellon
Summary
Programmers need a clear model of how variables are shared by threads.
Variables shared by multiple threads must be protected to ensure mutually exclusive access.
Semaphores are a fundamental mechanism for enforcing mutual exclusion.
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 75