Download as pdf or txt
Download as pdf or txt
You are on page 1of 17

Programming with Threads


CS 475


This lecture is based upon Sections 13.3-8 of Computer Systems: A Programmerʼs Perspective
by Bryant & OʼHalloran

Shared Variables in Threaded C


Programs

– 2 – CS 475

Page 1
Threads Memory Model

– 3 – CS 475

Example of Threads Accessing 



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

– 4 – CS 475

Page 2
Mapping Variables to Mem. Instances
Global var: 1 instance (ptr [data])

Local automatic vars: 1 instance (i.m, msgs.m )

char **ptr; /* global */ Local automatic var: 2 instances (


myid.p0[peer thread 0ʼs stack],
int main() myid.p1[peer thread 1ʼs stack]
{ )
int i;
pthread_t tid;
char *msgs[N] = { /* thread routine */
"Hello from foo", void *thread(void *vargp)
"Hello from bar" {
}; int myid = (int)vargp;
ptr = msgs; static int svar = 0;
for (i = 0; i < 2; i++)
Pthread_create(&tid, printf("[%d]: %s (svar=%d)\n",
NULL, myid, ptr[myid], ++svar);
thread, }
(void *)i);
Pthread_exit(NULL);
Local static var: 1 instance (svar [data])
}
– 5 – CS 475

Shared Variable Analysis

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

– 6 – CS 475

Page 3
badcnt.c: An Improperly
Synchronized Threaded Program
unsigned int cnt = 0; /* shared */ /* thread routine */
void *count(void *arg) {
int main() { int i;
pthread_t tid1, tid2; for (i=0; i<NITERS; i++)
Pthread_create(&tid1, NULL, cnt++;
count, NULL); return NULL;
Pthread_create(&tid2, NULL, }
count, NULL);
linux> ./badcnt
Pthread_join(tid1, NULL); BOOM! cnt=198841183
Pthread_join(tid2, NULL);
linux> ./badcnt
if (cnt != (unsigned)NITERS*2) BOOM! cnt=198261801
printf("BOOM! cnt=%d\n",
cnt); linux> ./badcnt
else BOOM! cnt=198269672
printf("OK cnt=%d\n",
cnt); cnt should be
} equal to 200,000,000.
What went wrong?!
– 7 – CS 475

Assembly Code for Counter Loop


C code for counter loop
for (i=0; i<NITERS; i++) Corresponding asm code
cnt++; (gcc -O0 -fforce-mem)
.L9:
movl -4(%ebp),%eax
Head (Hi) cmpl $99999999,%eax
jle .L12
jmp .L10
.L12:
Load cnt (Li) movl cnt,%eax # Load
Update cnt (Ui) leal 1(%eax),%edx # Update
Store cnt (Si) movl %edx,cnt # Store
.L11:
movl -4(%ebp),%eax
Tail (Ti) leal 1(%eax),%edx
movl %edx,-4(%ebp)
jmp .L9
.L10:

– 8 – CS 475

Page 4
Concurrent Execution

i (thread) instri %eax1 %eax2 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
OK

– 9 – CS 475

Concurrent Execution (cont)

i (thread) instri %eax1 %eax2 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
Oops!

– 10 – CS 475

Page 5
Concurrent Execution (cont)

i (thread) instri %eax1 %eax2 cnt


1 H1
1 L1
2 H2
2 L2
2 U2
2 S2
1 U1
1 S1
1 T1
2 T2

We can clarify our understanding of concurrent


execution with the help of the progress graph

– 11 – CS 475

Progress Graphs
Thread 2
A progress graph depicts
the discrete execution
state space of concurrent
T2 threads.
(L1, S2)
Each axis corresponds to
S2 the sequential order of
instructions in a thread.
U 2
Each point corresponds to
a possible execution state
L2 (Inst1, Inst2).

E.g., (L1, S2) denotes state


H 2
where thread 1 has
Thread 1 completed L1 and thread
H 1 L1 U 1 S1 T1
2 has completed S2.

– 12 – CS 475

Page 6
Trajectories in Progress Graphs
Thread 2
A trajectory is a sequence
of legal state transitions
T2 that describes one possible
concurrent execution of
the threads.
S2

Example:
U 2
H1, L1, U1, H2, L2,
S1, T1, U2, S2, T2
L2

H 2
Thread 1
H 1 L1 U 1 S1 T1

– 13 – CS 475

Critical Sections and Unsafe Regions


Thread 2 L, U, and S form a
critical section with
respect to the shared
T2 variable cnt.

S2 Instructions in critical
sections (wrt to some
critical shared variable) should
section U 2 Unsafe region not be interleaved.
wrt cnt
Sets of states where such
L2
interleaving occurs
form unsafe regions.
H 2
Thread 1
H 1 L1 U 1 S1 T1

critical section wrt cnt

– 14 – CS 475

Page 7
Safe and Unsafe Trajectories
Thread 2

T2 Safe trajectory
Def: A trajectory is safe
iff it doesnʼt touch any
S2 Unsafe region Unsafe part of an unsafe region.
trajectory
critical Claim: A trajectory is
section U 2
correct (wrt cnt) iff it is
wrt cnt
safe.
L2

H 2
Thread 1
H 1 L1 U 1 S1 T1

critical section wrt cnt

– 15 – CS 475

Semaphores

– 16 – CS 475

Page 8
Safe Sharing with Semaphores

/* 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;
}

– 17 – CS 475

Safe Sharing With Semaphores


Thread 2
1 1 0 0 0 0 1 1
Provide mutually
T2
1 1 0 0 0 0 1 1
exclusive access to
shared variable by
V(s) surrounding critical
0 0 Forbidden region 0 0
-1 -1 -1 -1
section with P and V
S2 operations on semaphore
0 0 0 0
-1 -1 -1 -1 s (initially set to 1).
U 2 Unsafe region
0 0 0 0
-1 -1 -1 -1 Semaphore invariant
L2 creates a forbidden region
-1 -1 -1 -1
0 0 0 0 that encloses unsafe
P(s) region and is never
1 1 0 0 0 0 1 1 touched by any trajectory.
H 2
1 1 0 0 0 0 1 1
H 1 P(s) L1 U 1 S1 V(s) T1
Initially Thread 1
s = 1
– 18 – CS 475

Page 9
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");
}

– 19 – CS 475

Sharing With POSIX Semaphores


/* goodcnt.c - properly sync’d /* thread routine */
counter program */ void *count(void *arg)
#include "csapp.h" {
#define NITERS 10000000 int i;

unsigned int cnt; /* counter */ for (i=0; i<NITERS; i++) {


sem_t sem; /* semaphore */ P(&sem);
cnt++;
int main() { V(&sem);
pthread_t tid1, tid2; }
return NULL;
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);
}
– 20 – CS 475

Page 10
Signaling With Semaphores
producer shared consumer
thread buffer thread

– 21 – CS 475

Producer-Consumer on a Buffer
That Holds One Item
/* buf1.c - producer-consumer int main() {
on 1-element buffer */ pthread_t tid_producer;
#include “csapp.h” pthread_t tid_consumer;

#define NITERS 5 /* initialize the semaphores */


Sem_init(&shared.empty, 0, 1);
void *producer(void *arg); Sem_init(&shared.full, 0, 0);
void *consumer(void *arg);
/* create threads and wait */
struct { Pthread_create(&tid_producer, NULL,
int buf; /* shared var */ producer, NULL);
sem_t full; /* sems */ Pthread_create(&tid_consumer, NULL,
sem_t empty; consumer, NULL);
} shared; Pthread_join(tid_producer, NULL);
Pthread_join(tid_consumer, NULL);

exit(0);
}

– 22 – CS 475

Page 11
Producer-Consumer (cont)
Initially: empty = 1, full = 0.

/* producer thread */ /* consumer thread */


void *producer(void *arg) { void *consumer(void *arg) {
int i, item; int i, item;

for (i=0; i<NITERS; i++) { for (i=0; i<NITERS; i++) {


/* produce item */ /* read item from buf */
item = i; P(&shared.full);
printf("produced %d\n", item = shared.buf;
item); V(&shared.empty);

/* write item to buf */ /* consume item */


P(&shared.empty); printf("consumed %d\n",
shared.buf = item; item);
V(&shared.full); }
} return NULL;
return NULL; }
}

– 23 – CS 475

Thread Safety

– 24 – CS 475

Page 12
Thread-Unsafe Functions

– 25 – CS 475

Thread-Unsafe Functions (cont)

/* rand - return pseudo-random integer on 0..32767 */


int rand(void)
{
static unsigned int next = 1;
next = next*1103515245 + 12345;
return (unsigned int)(next/65536) % 32768;
}

/* srand - set seed for rand() */


void srand(unsigned int seed)
{
next = seed;
}

– 26 – CS 475

Page 13
Thread-Unsafe Functions (cont)
struct hostent
*gethostbyname(char name)
{
static struct hostent h;
<contact DNS and fill in h>
return &h;
}

hostp = Malloc(...));
gethostbyname_r(name, hostp);

struct hostent
*gethostbyname_ts(char *p)
{
struct hostent *q = Malloc(...);
P(&mutex); /* lock */
p = gethostbyname(name);
*q = *p; /* copy */
V(&mutex);
return q;
}
– 27 – CS 475

Thread-Unsafe Functions

– 28 – CS 475

Page 14
Reentrant Functions

All functions
Thread-safe
functions
Thread-unsafe
functions
Reentrant
functions

– 29 – CS 475

Thread-Safe Library Functions

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

– 30 – CS 475

Page 15
Races

/* 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);
for (i = 0; i < N; 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;
– 31 – } CS 475

Deadlock
Locking introduces the
Thread 2 potential for deadlock:
V(s) deadlock waiting for a condition that
forbidden state will never be true.
region for s
Any trajectory that enters
V(t)
the deadlock region will
eventually reach the
deadlock state, waiting for
P(s)
either s or t to become
deadlock forbidden nonzero.
region region for t
P(t) Other trajectories luck out
and skirt the deadlock
region.

P(s) P(t) V(s) V(t) Thread 1 Unfortunate fact: deadlock


is often non-deterministic.
Initially, s=t=1
– 32 – CS 475

Page 16
Threads Summary

– 33 – CS 475

Page 17

You might also like