SlideShare a Scribd company logo
Carnegie Mellon
1
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Synchronization: Basics
15-213/14-513/15-513: Introduction to Computer Systems
24th Lecture, July 28, 2022
Instructor: Abi Kim
Carnegie Mellon
2
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Today
 Threads
 Sharing
 Mutual exclusion
Carnegie Mellon
3
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Traditional View of a Process
 Process = process context + code, data, and stack
Program context:
Data registers
Condition codes
Stack pointer (SP)
Program counter (PC)
Code, data, and stack
Stack
SP
Shared libraries
Run-time heap
0
Read/write data
Read-only code/data
PC
brk
Process context
Kernel context:
VM structures
Descriptor table
brk pointer
Carnegie Mellon
4
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Alternate View of a Process
 Process = thread + (code, data, and kernel context)
Shared libraries
Run-time heap
0
Read/write data
Thread context:
Data registers
Condition codes
Stack pointer (SP)
Program counter (PC)
Code, data, and kernel context
Read-only code/data
Stack
SP
PC
brk
Thread (main thread)
Kernel context:
VM structures
Descriptor table
brk pointer
Carnegie Mellon
5
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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 context:
Data registers
Condition codes
SP1
PC1
stack 1
Thread 1 (main thread)
shared libraries
run-time heap
0
read/write data
Shared code and data
read-only code/data
Kernel context:
VM structures
Descriptor table
brk pointer
Thread 2 context:
Data registers
Condition codes
SP2
PC2
stack 2
Thread 2 (peer thread)
Carnegie Mellon
6
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Memory is shared between all threads
Don’t let the picture confuse you!
stack 1
Thread 1 (main thread)
shared libraries
run-time heap
0
read/write data
Shared code and data
read-only code/data
Kernel context:
VM structures
Descriptor table
brk pointer
Thread 1 context:
Data registers
Condition codes
SP1
PC1
Thread 2 context:
Data registers
Condition codes
SP2
PC2
stack 2
Thread 2 (peer thread)
Carnegie Mellon
7
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Benefits of Threads
 Threads have lighter overhead
 Easier to share memory in concurrent programs using
threads
 Threads are faster due to multi-core CPUs allowing
multiple threads to execute at once
Carnegie Mellon
8
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Today
 Threads review
 Sharing
 Mutual exclusion
 Semaphores
Carnegie Mellon
9
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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?
Carnegie Mellon
10
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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 context:
Data registers
Condition codes
SP1
PC1
stack 1
Thread 1
(private) Shared code and data
shared libraries
run-time heap
read/write data
read-only code/data
Thread 2 context:
Data registers
Condition codes
SP2
PC2
stack 2
Thread 2
(private)
Carnegie Mellon
11
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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
The mismatch between the conceptual and operation model
is a source of confusion and errors
Thread 1 context:
Data registers
Condition codes
SP1
PC1
stack 1
Thread 1
(private)
Shared code and data
shared libraries
run-time heap
read/write data
read-only code/data
Thread 2 context:
Data registers
Condition codes
SP2
PC2
stack 2
Thread 2
(private)
Virtual Address Space
Carnegie Mellon
12
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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();
}
void *thread(void *vargp)
{
hist[*(long *)vargp] += 1;
Free(vargp);
return NULL;
}
void check(void) {
for (int i=0; i<N; i++) {
if (hist[i] != 1) {
printf("Failed at %dn", i);
exit(-1);
}
}
printf("OKn");
}
Carnegie Mellon
13
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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();
}
void *thread(void *vargp)
{
hist[*(long *)vargp] += 1;
Free(vargp);
return NULL;
}
• Use malloc to create a per
thread heap allocated
place in memory for the
argument
• Remember to free in
thread!
• Producer-consumer
pattern
Carnegie Mellon
14
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
int hist[N] = {0};
int main(int argc, char *argv[]) {
long i;
pthread_t tids[N];
for (i = 0; i < N; i++)
Pthread_create(&tids[i],
NULL,
thread,
(void *)i);
for (i = 0; i < N; i++)
Pthread_join(tids[i], NULL);
check();
}
void *thread(void *vargp)
{
hist[(long)vargp] += 1;
return NULL;
}
Passing an argument to a thread – Also OK!
• Ok to Use cast since
sizeof(long) <= sizeof(void*)
• Cast does NOT change bits
Carnegie Mellon
15
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
int hist[N] = {0};
int main(int argc, char *argv[]) {
long i;
pthread_t tids[N];
for (i = 0; i < N; i++)
Pthread_create(&tids[i],
NULL,
thread,
(void *)&i);
for (i = 0; i < N; i++)
Pthread_join(tids[i], NULL);
check();
}
void *thread(void *vargp)
{
hist[*(long*)vargp] += 1;
return NULL;
}
Passing an argument to a thread – WRONG!
• &i points to same location
for all threads!
• Creates a data race!
Carnegie Mellon
16
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Three Ways to Pass Thread Arg
 Malloc/free
 Producer malloc’s space, passes pointer to pthread_create
 Consumer dereferences pointer
 Ptr to stack slot
 Producer passes address to producer’s stack in pthread_create
 Consumer dereferences pointer
 Cast of int
 Producer casts an int/long to address in pthread_create
 Consumer casts void* argument back to int/long
Carnegie Mellon
17
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Example Program to Illustrate Sharing
char **ptr; /* global var */
int main(int argc, char *argv[])
{
long 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);
}
void *thread(void *vargp)
{
long myid = (long)vargp;
static int cnt = 0;
printf("[%ld]: %s (cnt=%d)n",
myid, ptr[myid], ++cnt);
return NULL;
}
Peer threads reference main thread’s stack
indirectly through global ptr variable
sharing.c
A common way to pass a single
argument to a thread routine
Carnegie Mellon
18
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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?
Carnegie Mellon
19
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Mapping Variable Instances to Memory
 Global variables
 Def: Variable declared outside of a function
 Virtual memory contains exactly one instance of any global variable
 Local variables
 Def: Variable declared inside function without static attribute
 Each thread stack contains one instance of each local variable
 Local static variables
 Def: Variable declared inside function with the static attribute
 Virtual memory contains exactly one instance of any local static
variable.
Carnegie Mellon
20
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
char **ptr; /* global var */
int main(int main, char *argv[])
{
long 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);
}
void *thread(void *vargp)
{
long myid = (long)vargp;
static int cnt = 0;
printf("[%ld]: %s (cnt=%d)n",
myid, ptr[myid], ++cnt);
return NULL;
}
Mapping Variable Instances to Memory
sharing.c
Carnegie Mellon
21
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
char **ptr; /* global var */
int main(int main, char *argv[])
{
long 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);
}
void *thread(void *vargp)
{
long myid = (long)vargp;
static int cnt = 0;
printf("[%ld]: %s (cnt=%d)n",
myid, ptr[myid], ++cnt);
return NULL;
}
Mapping Variable Instances to Memory
Global var: 1 instance (ptr [data])
Local static var: 1 instance (cnt [data])
Local vars: 1 instance (i.m, msgs.m, tid.m)
Local var: 2 instances (
myid.p0 [peer thread 0’s stack],
myid.p1 [peer thread 1’s stack]
)
sharing.c
Carnegie Mellon
22
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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, cnt, 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
cnt
i.m
msgs.m
myid.p0
myid.p1
yes yes yes
no yes yes
yes no no
yes yes yes
no yes no
no no yes
char **ptr; /* global var */
int main(int main, char *argv[]) {
long 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);}
void *thread(void *vargp)
{
long myid = (long)vargp;
static int cnt = 0;
printf("[%ld]: %s (cnt=%d)n",
myid, ptr[myid], ++cnt);
return NULL;
}
Carnegie Mellon
23
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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, cnt, 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
cnt
i.m
msgs.m
myid.p0
myid.p1
yes yes yes
no yes yes
yes no no
yes yes yes
no yes no
no no yes
Carnegie Mellon
24
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Synchronizing Threads
 Shared variables are handy...
 …but introduce the possibility of nasty synchronization
errors.
Carnegie Mellon
25
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
badcnt.c: Improper Synchronization
/* 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=%ldn", cnt);
else
printf("OK cnt=%ldn", cnt);
exit(0);
}
/* Thread routine */
void *thread(void *vargp)
{
long i, niters =
*((long *)vargp);
for (i = 0; i < niters; i++)
cnt++;
return NULL;
}
linux> ./badcnt 10000
OK cnt=20000
linux> ./badcnt 10000
BOOM! cnt=13051
linux>
cnt should equal 20,000.
What went wrong?
badcnt.c
Carnegie Mellon
26
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Assembly Code for Counter Loop
for (i = 0; i < niters; i++)
cnt++;
C code for counter loop in 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)
addq $1, %rax
cmpq %rcx, %rax
jne .L3
.L2:
Hi : Head
Ti : Tail
Li : Load cnt
Ui : Update cnt
Si : Store cnt
Asm code for thread i
Carnegie Mellon
27
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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
H1
L1
U1
S1
H2
L2
U2
S2
T2
T1
1
1
1
1
2
2
2
2
2
1
-
0
1
1
-
-
-
-
-
1
0
0
0
1
1
1
1
2
2
2
i (thread) instri cnt
%rdx1
OK
-
-
-
-
-
1
2
2
2
-
%rdx2
*For now. In reality, on x86 even non-sequentially consistent interleavings are possible
Carnegie Mellon
28
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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
H1
L1
U1
S1
H2
L2
U2
S2
T2
T1
1
1
1
1
2
2
2
2
2
1
-
0
1
1
-
-
-
-
-
1
0
0
0
1
1
1
1
2
2
2
i (thread) instri cnt
%rdx1
OK
-
-
-
-
-
1
2
2
2
-
%rdx2
Thread 1
critical section
Thread 2
critical section
Carnegie Mellon
29
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Concurrent Execution (cont)
 Incorrect ordering: two threads increment the counter,
but the result is 1 instead of 2
H1
L1
U1
H2
L2
S1
T1
U2
S2
T2
1
1
1
2
2
1
1
2
2
2
-
0
1
-
-
1
1
-
-
-
0
0
0
0
0
1
1
1
1
1
i (thread) instri cnt
%rdx1
-
-
-
-
0
-
-
1
1
1
%rdx2
Oops!
Carnegie Mellon
30
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Concurrent Execution (cont)
 How about this ordering?
 We can analyze the behavior using a progress graph
H1
L1
H2
L2
U2
S2
U1
S1
T1
T2
1
1
2
2
2
2
1
1
1
2
i (thread) instri cnt
%rdx1 %rdx2
0
0
0
1
1 1
1
1 1
1 Oops!
1
Carnegie Mellon
31
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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.
(L1, S2)
H1 L1 U1 S1 T1
H2
L2
U2
S2
T2
Thread 1
Thread 2
Carnegie Mellon
32
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Trajectories in Progress Graphs
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
H2
L2
U2
S2
T2
Thread 1
Thread 2
Carnegie Mellon
33
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Trajectories in Progress Graphs
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
H2
L2
U2
S2
T2
Thread 1
Thread 2
Carnegie Mellon
34
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Critical Sections and Unsafe Regions
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
H1 L1 U1 S1 T1
H2
L2
U2
S2
T2
Thread 1
Thread 2
critical section wrt cnt
critical
section
wrt
cnt
Unsafe region
Carnegie Mellon
35
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Critical Sections and Unsafe Regions
H1 L1 U1 S1 T1
H2
L2
U2
S2
T2
Thread 1
Thread 2
critical section wrt cnt
critical
section
wrt
cnt
Unsafe region
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
safe
Carnegie Mellon
36
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
badcnt.c: Improper Synchronization
/* 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=%ldn", cnt);
else
printf("OK cnt=%ldn", cnt);
exit(0);
}
/* Thread routine */
void *thread(void *vargp)
{
long i, niters =
*((long *)vargp);
for (i = 0; i < niters; i++)
cnt++;
return NULL;
}
badcnt.c
Variable main thread1 thread2
cnt
niters.m
tid1.m
i.1
i.2
niters.1
niters.2
Carnegie Mellon
37
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
badcnt.c: Improper Synchronization
/* 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=%ldn", cnt);
else
printf("OK cnt=%ldn", cnt);
exit(0);
}
/* Thread routine */
void *thread(void *vargp)
{
long i, niters =
*((long *)vargp);
for (i = 0; i < niters; i++)
cnt++;
return NULL;
}
badcnt.c
Variable main thread1 thread2
cnt yes* yes yes
niters.m yes no no
tid1.m yes no no
i.1 no yes no
i.2 no no yes
niters.1 no yes no
niters.2 no no yes
Carnegie Mellon
38
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Today
 Threads review
 Sharing
 Mutual exclusion
Carnegie Mellon
39
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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:
 Mutex (pthreads)
 Semaphores (Edsger Dijkstra)
 Other approaches (out of our scope)
 Condition variables (pthreads)
 Monitors (Java)
Carnegie Mellon
40
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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)
Carnegie Mellon
41
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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)
Carnegie Mellon
42
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
C Semaphore Operations
Pthreads functions:
#include <semaphore.h>
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 */
Carnegie Mellon
43
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
badcnt.c: Improper Synchronization
/* 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=%ldn", cnt);
else
printf("OK cnt=%ldn", cnt);
exit(0);
}
/* Thread routine */
void *thread(void *vargp)
{
long i, niters =
*((long *)vargp);
for (i = 0; i < niters; i++)
cnt++;
return NULL;
}
How can we fix this using
synchronization?
badcnt.c
Carnegie Mellon
44
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
MUTual EXclusion (mutex)
 Mutex: boolean synchronization variable
 enum {locked = 0, unlocked = 1}
 lock(m)
 If the mutex is currently not locked, lock it and return
 Otherwise, wait (spinning, yielding, etc) and retry
 unlock(m)
 Update the mutex state to unlocked
Carnegie Mellon
46
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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.
Carnegie Mellon
47
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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);
}
linux> ./goodcnt 10000
OK cnt=20000
linux> ./goodcnt 10000
OK cnt=20000
linux>
Warning: It’s orders of magnitude slower
than badcnt.c.
goodcnt.c
Carnegie Mellon
48
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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);
}
linux> ./goodcnt 10000
OK cnt=20000
linux> ./goodcnt 10000
OK cnt=20000
linux>
Warning: It’s orders of magnitude slower
than badcnt.c.
goodcnt.c
OK cnt=2000000 BOOM! cnt=1036525 Slowdown
real 0m0.138s 0m0.007s 20X
user 0m0.120s 0m0.008s 15X
sys 0m0.108s 0m0.000s NaN
And slower means much slower!
Carnegie Mellon
49
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Why Mutexes Work
Provide mutually exclusive
access to shared variable by
surrounding critical section
with lock and unlock
operations
H1 lo(m) un(m) T1
Thread 1
Thread 2
L1 U1 S1
H2
lo(m)
un(m)
T2
L2
U2
S2
Initially
m = 1
1 0 0 0
0
-1
Unsafe region
Carnegie Mellon
50
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Why Mutexes Work
Provide mutually exclusive
access to shared variable by
surrounding critical section
with lock and unlock
operations
Mutex invariant creates a
forbidden region that encloses
unsafe region and that cannot
be entered by any trajectory.
H1 lo(m) un(m) T1
Thread 1
Thread 2
L1 U1 S1
H2
lo(m)
un(m)
T2
L2
U2
S2
Initially
m = 1
1 0 0 0
0
-1
Unsafe region
Carnegie Mellon
51
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Why Mutexes Work
Provide mutually exclusive
access to shared variable by
surrounding critical section
with lock and unlock
operations
Mutex invariant creates a
forbidden region that encloses
unsafe region and that cannot
be entered by any trajectory.
H1 lo(m) un(m) T1
Thread 1
Thread 2
L1 U1 S1
H2
lo(m)
un(m)
T2
L2
U2
S2
Initially
m = 1
1 0 0 0
0
Unsafe region
0 1
0
Carnegie Mellon
52
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Unsafe region
Why Mutexes Work
Provide mutually exclusive
access to shared variable by
surrounding critical section
with lock and unlock
operations
Mutex invariant creates a
forbidden region that encloses
unsafe region and that cannot
be entered by any trajectory.
H1 lo(m) un(m) T1
Thread 1
Thread 2
L1 U1 S1
H2
lo(m)
un(m)
T2
L2
U2
S2
1 1 0 0 0 0 1 1
1 1 0 0 0 0 1 1
0 0 -1 -1 -1 -1 0 0
0 0
-1 -1 -1 -1
0 0
0 0
-1 -1 -1 -1
0 0
0 0
-1 -1 -1 -1
0 0
1 1 0 0 0 0 1 1
1 1 0 0 0 0 1 1
Initially
m = 1
Forbidden region
Carnegie Mellon
53
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
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.
Carnegie Mellon
54
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Appendix: Binary Semaphores vs.
Mutexes (not test material, just fyi!)
 Binary semaphore: semaphore initialized with a value of 1
 Both binary semaphores and mutexes can be used to
guarantee mutual exclusion
 Main difference is ownership
 Mutexes must be unlocked by the thread who owned them
previously
 Binary semaphores can be signaled/incremented (V) by a thread
who did not decrement (P) them
 As long as you use binary semaphores in the following
way in all threads, they can be used as a mutex
P(&sem);
// critical section
V(&sem); They are also implemented
differently but that’s out of
the scope for this class…
(covered in 15-410)

More Related Content

PPTX
05-machine-basics.pptx
HarinarayananRajeev1
 
PPTX
05-machine-basics.pptx
EricMax6
 
DOC
GSP 125 Doing by learn/newtonhelp.com
bellflower126
 
DOC
GSP 125 Perfect Education/newtonhelp.com
bellflower169
 
DOC
GSP 125 Become Exceptional/newtonhelp.com
bellflower148
 
DOC
Gsp 125 Future Our Mission/newtonhelp.com
amaranthbeg8
 
PDF
parallel-computation.pdf
Jayanti Prasad Ph.D.
 
DOCX
Due 24 August (Friday, 1159 p.m. EDT)Use Module 1 and Book Ch.docx
jacksnathalie
 
05-machine-basics.pptx
HarinarayananRajeev1
 
05-machine-basics.pptx
EricMax6
 
GSP 125 Doing by learn/newtonhelp.com
bellflower126
 
GSP 125 Perfect Education/newtonhelp.com
bellflower169
 
GSP 125 Become Exceptional/newtonhelp.com
bellflower148
 
Gsp 125 Future Our Mission/newtonhelp.com
amaranthbeg8
 
parallel-computation.pdf
Jayanti Prasad Ph.D.
 
Due 24 August (Friday, 1159 p.m. EDT)Use Module 1 and Book Ch.docx
jacksnathalie
 

Similar to 24-sync-basic.pptx (20)

PDF
Parallel computation
Jayanti Prasad Ph.D.
 
PPT
lec4.ppt system calls explained in detail
frp60658
 
DOC
GSP 125 Education Specialist / snaptutorial.com
stevesonz146
 
DOCX
GSP 125 Exceptional Education - snaptutorial.com
donaldzs162
 
DOCX
Gsp 125 Education Organization -- snaptutorial.com
DavisMurphyB85
 
DOC
Gsp 125 Enthusiastic Study / snaptutorial.com
Stephenson101
 
DOC
GSP 125 Technology levels--snaptutorial.com
sholingarjosh136
 
DOC
Gsp 125 Massive Success / snaptutorial.com
NorrisMistryzo
 
DOCX
GSP 125 Enhance teaching - snaptutorial.com
DavisMurphyA81
 
PPTX
WEEK07operatingsystemdepartmentofsoftwareengineering.pptx
babayaga920391
 
PPTX
05-machine-basics.pptx
MadrasAttiChannel
 
PDF
final_2014.pdf
RavinderKSingla
 
PPTX
Threads and multi threading
Antonio Cesarano
 
ODP
Distributed Coordination
Luis Galárraga
 
DOCX
GSP 125 RANK Education for Service--gsp125rank.com
claric25
 
PPT
C language
spatidar0
 
PDF
C++ neural networks and fuzzy logic
Jamerson Ramos
 
DOCX
GSP 125 Enhance teaching/tutorialrank.com
jonhson300
 
DOCX
GSP 125 Effective Communication/tutorialrank.com
jonhson282
 
DOCX
GSP 125 Entire Course NEW
shyamuopten
 
Parallel computation
Jayanti Prasad Ph.D.
 
lec4.ppt system calls explained in detail
frp60658
 
GSP 125 Education Specialist / snaptutorial.com
stevesonz146
 
GSP 125 Exceptional Education - snaptutorial.com
donaldzs162
 
Gsp 125 Education Organization -- snaptutorial.com
DavisMurphyB85
 
Gsp 125 Enthusiastic Study / snaptutorial.com
Stephenson101
 
GSP 125 Technology levels--snaptutorial.com
sholingarjosh136
 
Gsp 125 Massive Success / snaptutorial.com
NorrisMistryzo
 
GSP 125 Enhance teaching - snaptutorial.com
DavisMurphyA81
 
WEEK07operatingsystemdepartmentofsoftwareengineering.pptx
babayaga920391
 
05-machine-basics.pptx
MadrasAttiChannel
 
final_2014.pdf
RavinderKSingla
 
Threads and multi threading
Antonio Cesarano
 
Distributed Coordination
Luis Galárraga
 
GSP 125 RANK Education for Service--gsp125rank.com
claric25
 
C language
spatidar0
 
C++ neural networks and fuzzy logic
Jamerson Ramos
 
GSP 125 Enhance teaching/tutorialrank.com
jonhson300
 
GSP 125 Effective Communication/tutorialrank.com
jonhson282
 
GSP 125 Entire Course NEW
shyamuopten
 

Recently uploaded (20)

PPTX
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
PPTX
cloud computing vai.pptx for the project
vaibhavdobariyal79
 
PDF
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
PDF
BLW VOCATIONAL TRAINING SUMMER INTERNSHIP REPORT
codernjn73
 
PDF
Accelerating Oracle Database 23ai Troubleshooting with Oracle AHF Fleet Insig...
Sandesh Rao
 
PDF
Oracle AI Vector Search- Getting Started and what's new in 2025- AIOUG Yatra ...
Sandesh Rao
 
PDF
NewMind AI Weekly Chronicles - July'25 - Week IV
NewMind AI
 
PDF
AI Unleashed - Shaping the Future -Starting Today - AIOUG Yatra 2025 - For Co...
Sandesh Rao
 
PDF
Brief History of Internet - Early Days of Internet
sutharharshit158
 
PDF
Using Anchore and DefectDojo to Stand Up Your DevSecOps Function
Anchore
 
PDF
The Evolution of KM Roles (Presented at Knowledge Summit Dublin 2025)
Enterprise Knowledge
 
PDF
OFFOFFBOX™ – A New Era for African Film | Startup Presentation
ambaicciwalkerbrian
 
PDF
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
PDF
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
PPTX
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
PPTX
Applied-Statistics-Mastering-Data-Driven-Decisions.pptx
parmaryashparmaryash
 
PDF
Google I/O Extended 2025 Baku - all ppts
HusseinMalikMammadli
 
PDF
Data_Analytics_vs_Data_Science_vs_BI_by_CA_Suvidha_Chaplot.pdf
CA Suvidha Chaplot
 
PDF
MASTERDECK GRAPHSUMMIT SYDNEY (Public).pdf
Neo4j
 
PDF
A Strategic Analysis of the MVNO Wave in Emerging Markets.pdf
IPLOOK Networks
 
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
cloud computing vai.pptx for the project
vaibhavdobariyal79
 
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
BLW VOCATIONAL TRAINING SUMMER INTERNSHIP REPORT
codernjn73
 
Accelerating Oracle Database 23ai Troubleshooting with Oracle AHF Fleet Insig...
Sandesh Rao
 
Oracle AI Vector Search- Getting Started and what's new in 2025- AIOUG Yatra ...
Sandesh Rao
 
NewMind AI Weekly Chronicles - July'25 - Week IV
NewMind AI
 
AI Unleashed - Shaping the Future -Starting Today - AIOUG Yatra 2025 - For Co...
Sandesh Rao
 
Brief History of Internet - Early Days of Internet
sutharharshit158
 
Using Anchore and DefectDojo to Stand Up Your DevSecOps Function
Anchore
 
The Evolution of KM Roles (Presented at Knowledge Summit Dublin 2025)
Enterprise Knowledge
 
OFFOFFBOX™ – A New Era for African Film | Startup Presentation
ambaicciwalkerbrian
 
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
Applied-Statistics-Mastering-Data-Driven-Decisions.pptx
parmaryashparmaryash
 
Google I/O Extended 2025 Baku - all ppts
HusseinMalikMammadli
 
Data_Analytics_vs_Data_Science_vs_BI_by_CA_Suvidha_Chaplot.pdf
CA Suvidha Chaplot
 
MASTERDECK GRAPHSUMMIT SYDNEY (Public).pdf
Neo4j
 
A Strategic Analysis of the MVNO Wave in Emerging Markets.pdf
IPLOOK Networks
 

24-sync-basic.pptx

  • 1. Carnegie Mellon 1 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Synchronization: Basics 15-213/14-513/15-513: Introduction to Computer Systems 24th Lecture, July 28, 2022 Instructor: Abi Kim
  • 2. Carnegie Mellon 2 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Today  Threads  Sharing  Mutual exclusion
  • 3. Carnegie Mellon 3 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Traditional View of a Process  Process = process context + code, data, and stack Program context: Data registers Condition codes Stack pointer (SP) Program counter (PC) Code, data, and stack Stack SP Shared libraries Run-time heap 0 Read/write data Read-only code/data PC brk Process context Kernel context: VM structures Descriptor table brk pointer
  • 4. Carnegie Mellon 4 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Alternate View of a Process  Process = thread + (code, data, and kernel context) Shared libraries Run-time heap 0 Read/write data Thread context: Data registers Condition codes Stack pointer (SP) Program counter (PC) Code, data, and kernel context Read-only code/data Stack SP PC brk Thread (main thread) Kernel context: VM structures Descriptor table brk pointer
  • 5. Carnegie Mellon 5 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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 context: Data registers Condition codes SP1 PC1 stack 1 Thread 1 (main thread) shared libraries run-time heap 0 read/write data Shared code and data read-only code/data Kernel context: VM structures Descriptor table brk pointer Thread 2 context: Data registers Condition codes SP2 PC2 stack 2 Thread 2 (peer thread)
  • 6. Carnegie Mellon 6 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Memory is shared between all threads Don’t let the picture confuse you! stack 1 Thread 1 (main thread) shared libraries run-time heap 0 read/write data Shared code and data read-only code/data Kernel context: VM structures Descriptor table brk pointer Thread 1 context: Data registers Condition codes SP1 PC1 Thread 2 context: Data registers Condition codes SP2 PC2 stack 2 Thread 2 (peer thread)
  • 7. Carnegie Mellon 7 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Benefits of Threads  Threads have lighter overhead  Easier to share memory in concurrent programs using threads  Threads are faster due to multi-core CPUs allowing multiple threads to execute at once
  • 8. Carnegie Mellon 8 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Today  Threads review  Sharing  Mutual exclusion  Semaphores
  • 9. Carnegie Mellon 9 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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?
  • 10. Carnegie Mellon 10 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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 context: Data registers Condition codes SP1 PC1 stack 1 Thread 1 (private) Shared code and data shared libraries run-time heap read/write data read-only code/data Thread 2 context: Data registers Condition codes SP2 PC2 stack 2 Thread 2 (private)
  • 11. Carnegie Mellon 11 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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 The mismatch between the conceptual and operation model is a source of confusion and errors Thread 1 context: Data registers Condition codes SP1 PC1 stack 1 Thread 1 (private) Shared code and data shared libraries run-time heap read/write data read-only code/data Thread 2 context: Data registers Condition codes SP2 PC2 stack 2 Thread 2 (private) Virtual Address Space
  • 12. Carnegie Mellon 12 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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(); } void *thread(void *vargp) { hist[*(long *)vargp] += 1; Free(vargp); return NULL; } void check(void) { for (int i=0; i<N; i++) { if (hist[i] != 1) { printf("Failed at %dn", i); exit(-1); } } printf("OKn"); }
  • 13. Carnegie Mellon 13 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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(); } void *thread(void *vargp) { hist[*(long *)vargp] += 1; Free(vargp); return NULL; } • Use malloc to create a per thread heap allocated place in memory for the argument • Remember to free in thread! • Producer-consumer pattern
  • 14. Carnegie Mellon 14 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition int hist[N] = {0}; int main(int argc, char *argv[]) { long i; pthread_t tids[N]; for (i = 0; i < N; i++) Pthread_create(&tids[i], NULL, thread, (void *)i); for (i = 0; i < N; i++) Pthread_join(tids[i], NULL); check(); } void *thread(void *vargp) { hist[(long)vargp] += 1; return NULL; } Passing an argument to a thread – Also OK! • Ok to Use cast since sizeof(long) <= sizeof(void*) • Cast does NOT change bits
  • 15. Carnegie Mellon 15 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition int hist[N] = {0}; int main(int argc, char *argv[]) { long i; pthread_t tids[N]; for (i = 0; i < N; i++) Pthread_create(&tids[i], NULL, thread, (void *)&i); for (i = 0; i < N; i++) Pthread_join(tids[i], NULL); check(); } void *thread(void *vargp) { hist[*(long*)vargp] += 1; return NULL; } Passing an argument to a thread – WRONG! • &i points to same location for all threads! • Creates a data race!
  • 16. Carnegie Mellon 16 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Three Ways to Pass Thread Arg  Malloc/free  Producer malloc’s space, passes pointer to pthread_create  Consumer dereferences pointer  Ptr to stack slot  Producer passes address to producer’s stack in pthread_create  Consumer dereferences pointer  Cast of int  Producer casts an int/long to address in pthread_create  Consumer casts void* argument back to int/long
  • 17. Carnegie Mellon 17 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Example Program to Illustrate Sharing char **ptr; /* global var */ int main(int argc, char *argv[]) { long 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); } void *thread(void *vargp) { long myid = (long)vargp; static int cnt = 0; printf("[%ld]: %s (cnt=%d)n", myid, ptr[myid], ++cnt); return NULL; } Peer threads reference main thread’s stack indirectly through global ptr variable sharing.c A common way to pass a single argument to a thread routine
  • 18. Carnegie Mellon 18 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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?
  • 19. Carnegie Mellon 19 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Mapping Variable Instances to Memory  Global variables  Def: Variable declared outside of a function  Virtual memory contains exactly one instance of any global variable  Local variables  Def: Variable declared inside function without static attribute  Each thread stack contains one instance of each local variable  Local static variables  Def: Variable declared inside function with the static attribute  Virtual memory contains exactly one instance of any local static variable.
  • 20. Carnegie Mellon 20 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition char **ptr; /* global var */ int main(int main, char *argv[]) { long 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); } void *thread(void *vargp) { long myid = (long)vargp; static int cnt = 0; printf("[%ld]: %s (cnt=%d)n", myid, ptr[myid], ++cnt); return NULL; } Mapping Variable Instances to Memory sharing.c
  • 21. Carnegie Mellon 21 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition char **ptr; /* global var */ int main(int main, char *argv[]) { long 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); } void *thread(void *vargp) { long myid = (long)vargp; static int cnt = 0; printf("[%ld]: %s (cnt=%d)n", myid, ptr[myid], ++cnt); return NULL; } Mapping Variable Instances to Memory Global var: 1 instance (ptr [data]) Local static var: 1 instance (cnt [data]) Local vars: 1 instance (i.m, msgs.m, tid.m) Local var: 2 instances ( myid.p0 [peer thread 0’s stack], myid.p1 [peer thread 1’s stack] ) sharing.c
  • 22. Carnegie Mellon 22 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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, cnt, 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 cnt i.m msgs.m myid.p0 myid.p1 yes yes yes no yes yes yes no no yes yes yes no yes no no no yes char **ptr; /* global var */ int main(int main, char *argv[]) { long 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);} void *thread(void *vargp) { long myid = (long)vargp; static int cnt = 0; printf("[%ld]: %s (cnt=%d)n", myid, ptr[myid], ++cnt); return NULL; }
  • 23. Carnegie Mellon 23 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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, cnt, 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 cnt i.m msgs.m myid.p0 myid.p1 yes yes yes no yes yes yes no no yes yes yes no yes no no no yes
  • 24. Carnegie Mellon 24 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Synchronizing Threads  Shared variables are handy...  …but introduce the possibility of nasty synchronization errors.
  • 25. Carnegie Mellon 25 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition badcnt.c: Improper Synchronization /* 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=%ldn", cnt); else printf("OK cnt=%ldn", cnt); exit(0); } /* Thread routine */ void *thread(void *vargp) { long i, niters = *((long *)vargp); for (i = 0; i < niters; i++) cnt++; return NULL; } linux> ./badcnt 10000 OK cnt=20000 linux> ./badcnt 10000 BOOM! cnt=13051 linux> cnt should equal 20,000. What went wrong? badcnt.c
  • 26. Carnegie Mellon 26 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Assembly Code for Counter Loop for (i = 0; i < niters; i++) cnt++; C code for counter loop in 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) addq $1, %rax cmpq %rcx, %rax jne .L3 .L2: Hi : Head Ti : Tail Li : Load cnt Ui : Update cnt Si : Store cnt Asm code for thread i
  • 27. Carnegie Mellon 27 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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 H1 L1 U1 S1 H2 L2 U2 S2 T2 T1 1 1 1 1 2 2 2 2 2 1 - 0 1 1 - - - - - 1 0 0 0 1 1 1 1 2 2 2 i (thread) instri cnt %rdx1 OK - - - - - 1 2 2 2 - %rdx2 *For now. In reality, on x86 even non-sequentially consistent interleavings are possible
  • 28. Carnegie Mellon 28 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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 H1 L1 U1 S1 H2 L2 U2 S2 T2 T1 1 1 1 1 2 2 2 2 2 1 - 0 1 1 - - - - - 1 0 0 0 1 1 1 1 2 2 2 i (thread) instri cnt %rdx1 OK - - - - - 1 2 2 2 - %rdx2 Thread 1 critical section Thread 2 critical section
  • 29. Carnegie Mellon 29 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Concurrent Execution (cont)  Incorrect ordering: two threads increment the counter, but the result is 1 instead of 2 H1 L1 U1 H2 L2 S1 T1 U2 S2 T2 1 1 1 2 2 1 1 2 2 2 - 0 1 - - 1 1 - - - 0 0 0 0 0 1 1 1 1 1 i (thread) instri cnt %rdx1 - - - - 0 - - 1 1 1 %rdx2 Oops!
  • 30. Carnegie Mellon 30 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Concurrent Execution (cont)  How about this ordering?  We can analyze the behavior using a progress graph H1 L1 H2 L2 U2 S2 U1 S1 T1 T2 1 1 2 2 2 2 1 1 1 2 i (thread) instri cnt %rdx1 %rdx2 0 0 0 1 1 1 1 1 1 1 Oops! 1
  • 31. Carnegie Mellon 31 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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. (L1, S2) H1 L1 U1 S1 T1 H2 L2 U2 S2 T2 Thread 1 Thread 2
  • 32. Carnegie Mellon 32 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Trajectories in Progress Graphs 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 H2 L2 U2 S2 T2 Thread 1 Thread 2
  • 33. Carnegie Mellon 33 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Trajectories in Progress Graphs 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 H2 L2 U2 S2 T2 Thread 1 Thread 2
  • 34. Carnegie Mellon 34 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Critical Sections and Unsafe Regions 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 H1 L1 U1 S1 T1 H2 L2 U2 S2 T2 Thread 1 Thread 2 critical section wrt cnt critical section wrt cnt Unsafe region
  • 35. Carnegie Mellon 35 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Critical Sections and Unsafe Regions H1 L1 U1 S1 T1 H2 L2 U2 S2 T2 Thread 1 Thread 2 critical section wrt cnt critical section wrt cnt Unsafe region 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 safe
  • 36. Carnegie Mellon 36 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition badcnt.c: Improper Synchronization /* 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=%ldn", cnt); else printf("OK cnt=%ldn", cnt); exit(0); } /* Thread routine */ void *thread(void *vargp) { long i, niters = *((long *)vargp); for (i = 0; i < niters; i++) cnt++; return NULL; } badcnt.c Variable main thread1 thread2 cnt niters.m tid1.m i.1 i.2 niters.1 niters.2
  • 37. Carnegie Mellon 37 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition badcnt.c: Improper Synchronization /* 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=%ldn", cnt); else printf("OK cnt=%ldn", cnt); exit(0); } /* Thread routine */ void *thread(void *vargp) { long i, niters = *((long *)vargp); for (i = 0; i < niters; i++) cnt++; return NULL; } badcnt.c Variable main thread1 thread2 cnt yes* yes yes niters.m yes no no tid1.m yes no no i.1 no yes no i.2 no no yes niters.1 no yes no niters.2 no no yes
  • 38. Carnegie Mellon 38 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Today  Threads review  Sharing  Mutual exclusion
  • 39. Carnegie Mellon 39 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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:  Mutex (pthreads)  Semaphores (Edsger Dijkstra)  Other approaches (out of our scope)  Condition variables (pthreads)  Monitors (Java)
  • 40. Carnegie Mellon 40 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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)
  • 41. Carnegie Mellon 41 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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)
  • 42. Carnegie Mellon 42 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition C Semaphore Operations Pthreads functions: #include <semaphore.h> 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 */
  • 43. Carnegie Mellon 43 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition badcnt.c: Improper Synchronization /* 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=%ldn", cnt); else printf("OK cnt=%ldn", cnt); exit(0); } /* Thread routine */ void *thread(void *vargp) { long i, niters = *((long *)vargp); for (i = 0; i < niters; i++) cnt++; return NULL; } How can we fix this using synchronization? badcnt.c
  • 44. Carnegie Mellon 44 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition MUTual EXclusion (mutex)  Mutex: boolean synchronization variable  enum {locked = 0, unlocked = 1}  lock(m)  If the mutex is currently not locked, lock it and return  Otherwise, wait (spinning, yielding, etc) and retry  unlock(m)  Update the mutex state to unlocked
  • 45. Carnegie Mellon 46 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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.
  • 46. Carnegie Mellon 47 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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); } linux> ./goodcnt 10000 OK cnt=20000 linux> ./goodcnt 10000 OK cnt=20000 linux> Warning: It’s orders of magnitude slower than badcnt.c. goodcnt.c
  • 47. Carnegie Mellon 48 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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); } linux> ./goodcnt 10000 OK cnt=20000 linux> ./goodcnt 10000 OK cnt=20000 linux> Warning: It’s orders of magnitude slower than badcnt.c. goodcnt.c OK cnt=2000000 BOOM! cnt=1036525 Slowdown real 0m0.138s 0m0.007s 20X user 0m0.120s 0m0.008s 15X sys 0m0.108s 0m0.000s NaN And slower means much slower!
  • 48. Carnegie Mellon 49 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Why Mutexes Work Provide mutually exclusive access to shared variable by surrounding critical section with lock and unlock operations H1 lo(m) un(m) T1 Thread 1 Thread 2 L1 U1 S1 H2 lo(m) un(m) T2 L2 U2 S2 Initially m = 1 1 0 0 0 0 -1 Unsafe region
  • 49. Carnegie Mellon 50 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Why Mutexes Work Provide mutually exclusive access to shared variable by surrounding critical section with lock and unlock operations Mutex invariant creates a forbidden region that encloses unsafe region and that cannot be entered by any trajectory. H1 lo(m) un(m) T1 Thread 1 Thread 2 L1 U1 S1 H2 lo(m) un(m) T2 L2 U2 S2 Initially m = 1 1 0 0 0 0 -1 Unsafe region
  • 50. Carnegie Mellon 51 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Why Mutexes Work Provide mutually exclusive access to shared variable by surrounding critical section with lock and unlock operations Mutex invariant creates a forbidden region that encloses unsafe region and that cannot be entered by any trajectory. H1 lo(m) un(m) T1 Thread 1 Thread 2 L1 U1 S1 H2 lo(m) un(m) T2 L2 U2 S2 Initially m = 1 1 0 0 0 0 Unsafe region 0 1 0
  • 51. Carnegie Mellon 52 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Unsafe region Why Mutexes Work Provide mutually exclusive access to shared variable by surrounding critical section with lock and unlock operations Mutex invariant creates a forbidden region that encloses unsafe region and that cannot be entered by any trajectory. H1 lo(m) un(m) T1 Thread 1 Thread 2 L1 U1 S1 H2 lo(m) un(m) T2 L2 U2 S2 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 0 0 -1 -1 -1 -1 0 0 0 0 -1 -1 -1 -1 0 0 0 0 -1 -1 -1 -1 0 0 0 0 -1 -1 -1 -1 0 0 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 Initially m = 1 Forbidden region
  • 52. Carnegie Mellon 53 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition 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.
  • 53. Carnegie Mellon 54 Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition Appendix: Binary Semaphores vs. Mutexes (not test material, just fyi!)  Binary semaphore: semaphore initialized with a value of 1  Both binary semaphores and mutexes can be used to guarantee mutual exclusion  Main difference is ownership  Mutexes must be unlocked by the thread who owned them previously  Binary semaphores can be signaled/incremented (V) by a thread who did not decrement (P) them  As long as you use binary semaphores in the following way in all threads, they can be used as a mutex P(&sem); // critical section V(&sem); They are also implemented differently but that’s out of the scope for this class… (covered in 15-410)