Class17 Threads1
Class17 Threads1
Topics
Threads
Shared variables
The need for synchronization
Synchronizing with semaphores
Thread safety and reentrancy
Races and deadlocks
threads1.ppt
Traditional View of a Process
Process = process context + code, data, and stack
–2– CS 105
Alternate View of a Process
Process = thread + code, data, and kernel context
shared libraries
stack
SP brk
run-time heap
read/write data
Thread context:
Data registers PC read-only code/data
Condition codes
0
Stack pointer (SP)
Program counter (PC)
Kernel context:
VM structures
Descriptor table
brk pointer
–3– CS 105
A Process With Multiple Threads
Multiple threads can be associated with a process
Each thread has its own logical control flow (sequence of PC
values)
Each thread shares the same code, data, and kernel context
Each thread has its own thread id (TID)
Thread 1 (main thread) Shared code and data Thread 2 (peer thread)
shared libraries
stack 1 stack 2
run-time heap
T2 P0
T4
T1
P1
shared code, data
and kernel context
sh sh sh
T5 T3
foo
bar
–5– CS 105
Concurrent Thread Execution
Two threads run concurrently (are concurrent) if their
logical flows overlap in time
Otherwise, they are sequential (just like processes)
Time
–6– CS 105
Threads vs. Processes
How threads and processes are similar
Each has its own logical control flow
Each can run concurrently
Each is context switched
–7– CS 105
Posix Threads (Pthreads)
Interface
Pthreads: Standard interface for ~60 functions that
manipulate threads from C programs
Creating and reaping threads
pthread_create, pthread_join
Determining your thread ID
pthread_self
Terminating threads
pthread_cancel, pthread_exit
exit [terminates all threads] , return [terminates current
thread]
Synchronizing access to shared variables
pthread_mutex_init, pthread_mutex_[un]lock
pthread_cond_init, pthread_cond_[timed]wait
–8– CS 105
The Pthreads "hello, world"
Program
/*
* hello.c - Pthreads "hello, world" program
*/
#include "csapp.h" Thread attributes
(usually NULL)
void *howdy(void *vargp);
–9– CS 105
Execution of Threaded “hello,
world”
main thread
call Pthread_create()
Pthread_create() returns peer thread
call Pthread_join()
printf()
main thread waits for return NULL;
peer thread to terminate (peer thread
terminates)
Pthread_join() returns
exit()
terminates
main thread and
any peer threads
– 10 – CS 105
Pros and Cons
of Thread-Based Designs
+ Easy to share data structures between threads
E.g., logging information, file cache
– 11 – CS 105
Shared Variables in Threaded
C Programs
Question: Which variables in a threaded C program are
shared variables?
Answer not as simple as “global variables are shared” and
“stack variables are private”
– 12 – CS 105
Threads Memory Model
Conceptual model:
Each thread runs in the context of a process
Each thread has its own separate thread context
Thread ID, stack, stack pointer, program counter, condition codes, and
general purpose registers
All threads share remaining process context
Code, data, heap, and shared library segments of process virtual
address space
Open files and installed handlers
– 14 – CS 105
Mapping Vars to Mem. Instances
Global var: 1 instance (ptr [data])
Answer: A variable x is shared iff multiple threads reference at least one instance of x. Thus:
ptr, svar, and msgs are shared.
i and myid are NOT shared.
– 16 – CS 105
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
} 200,000,000.
What went wrong?!
– 17 – CS 105
Assembly Code for Counter
Loop
C code for counter loop
for (i=0; i<NITERS; i++) Corresponding asm code
cnt++;
.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:
– 18 – CS 105
Concurrent Execution
Key idea: In general, any sequentially consistent
interleaving is possible, but some are incorrect!
Ii denotes that thread i executes instruction I
%eaxi is the contents of %eax in thread i’s context
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
– 19 – CS 105
What Is
“Sequential Consistency?”
Two (or more) parallel executions are sequentially
consistent iff instructions of each thread (or
process) are executed in sequential order
No restrictions on how threads relate to each other
Each thread runs at arbitrary speed
Any interleaving is legitimate
Any (or all) instructions of B can run between any two
instructions of A
– 20 – CS 105
Concurrent Execution (cont)
Incorrect ordering: two threads increment the counter,
but the result is 1 instead of 2
– 21 – CS 105
Concurrent Execution (cont)
How about this ordering?
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
– 22 – CS 105
Progress Graphs
A progress graph depicts
Thread 2 the discrete execution
state space of concurrent
threads.
T2
(L1, S2)
Each axis corresponds to
S2 the sequential order of
instructions in a thread.
U2
Each point corresponds to
a possible execution state
L2 (Inst1, Inst2).
– 23 – CS 105
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:
U2
H1, L1, U1, H2, L2,
S1, T1, U2, S2, T2
L2
H2
Thread 1
H1 L1 U1 S1 T1
– 24 – CS 105
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 U2 Unsafe region not be interleaved.
wrt cnt
L2
Sets of states where such
interleaving occurs
form unsafe regions.
H2
Thread 1
H1 L1 U1 S1 T1
– 25 – CS 105
Safe and Unsafe Trajectories
Thread 2
T2 Safe trajectory
Def: A trajectory is safe
iff it doesn’t enter any
S2 Unsafe region Unsafe part of an unsafe region.
trajectory
critical Claim: A trajectory is
section U2
correct (wrt cnt) iff it is
wrt cnt
safe.
L2
H2
Thread 1
H1 L1 U1 S1 T1
– 26 – CS 105
Semaphores
Question: How can we guarantee a safe trajectory?
Synchronize threads so they never enter unsafe state
Classic solution: Dijkstra's P and V operations on
semaphores
Semaphore: non-negative integer synchronization variable
P(s): while(1){[ if (s>0) {s--; break}] wait_a_while()}
» Dutch for “test” ("Proberen“)
V(s): [ s++; ]
» Dutch for “increment” ("Verhogen")
OS guarantees that operations between brackets [ ] are
executed indivisibly
Only one P or V operation at a time can modify s
When while loop in process X terminates, only that process has
decremented s
/* Semaphore s is initially 1 */
/* Thread routine */
void *count(void *arg)
{
int i;
– 28 – CS 105
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).
U2 Unsafe region
0 0 0 0
-1 -1 -1 -1 Semaphore invariant
L2 creates 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.
H2
1 1 0 0 0 0 1 1
H1 P(s) L1 U1 S1 V(s) T1
Initially Thread 1
s=1
– 29 – CS 105
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) == -1)
unix_error("Sem_init");
}
– 30 – CS 105
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;
if (cnt != (unsigned)NITERS*2)
printf("BOOM! cnt=%d\n", cnt);
else
printf("OK cnt=%d\n", cnt);
exit(0);
}
– 31 – CS 105
Signaling With Semaphores
producer shared consumer
thread buffer thread
Examples
Multimedia processing:
Producer creates MPEG video frames, consumer renders the frames
Event-driven graphical user interfaces
Producer detects mouse clicks, mouse movements, and keystrokes and
inserts corresponding events in buffer
Consumer retrieves events from buffer and paints display
– 32 – CS 105
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;
exit(0);
}
– 33 – CS 105
Producer-Consumer (cont)
Initially: empty = 1, full = 0.
– 34 – CS 105
Thread Safety
Functions called from a thread must be thread-safe
– 35 – CS 105
Thread-Unsafe Functions
Class 1: Failing to protect shared variables
Fix: Use P and V semaphore operations
Issue: Synchronization operations will slow down code
Example: goodcnt.c
– 36 – CS 105
Thread-Unsafe Functions (cont)
Class 2: Relying on persistent state across multiple
function invocations
Random number generator relies on static state
Fix: Rewrite function so that caller passes in all necessary
state
– 37 – CS 105
Thread-Unsafe Functions (cont)
Class 3: Returning pointer struct hostent
*gethostbyname(char* name)
to static variable {
static struct hostent h;
Fixes: <contact DNS and fill in h>
1. Rewrite code so caller return &h;
}
passes pointer to struct
» Issue: Requires hostp = Malloc(...));
changes in caller gethostbyname_r(name, hostp);
and callee
2. Lock-and-copy
struct hostent
» Issue: Requires only
*gethostbyname_ts(char *p)
simple changes in {
caller (and none in struct hostent *q = Malloc(...);
callee) P(&mutex); /* lock */
» However, caller must p = gethostbyname(name);
*q = *p; /* copy */
free memory
V(&mutex);
return q;
}
– 38 – CS 105
Thread-Unsafe Functions
Class 4: Calling thread-unsafe functions
Calling one thread-unsafe function makes an entire function
thread-unsafe
– 39 – CS 105
Reentrant Functions
A function is reentrant iff it accesses NO shared variables when called from multiple threads
Reentrant functions are a proper subset of the set of thread-safe functions
All functions
Thread-safe
NOTE: The fixes to Class 2 and 3 thread-unsafe functions require modifying the function to make it reentrant (only first fix for Class 3 is reentrant)
functions
Thread-unsafe
functions
Reentrant
functions
– 40 – CS 105
Thread-Safe Library Functions
Most functions in the Standard C Library (at the back of
your K&R text) are thread-safe
Examples: malloc, free, printf, scanf
– 41 – CS 105
Races
Race happens when program correctness depends on one
thread reaching point x before another thread reaches
point y
/* a threaded program with a race */
int main() {
pthread_t tid[N];
int i;
for (i = 0; i < N; i++)
Pthread_create(&tid[i], NULL, thread, &i);
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;
– 42 – } CS 105
Deadlock
Locking introduces
Thread 2 potential for deadlock:
V(s) deadlock waiting for a condition
forbidden state that will never be true.
region for s
Any trajectory that enters
V(t)
deadlock region will
eventually reach
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 deadlock region.
– 44 – CS 105