programming with threads

44
Programming with Threads Topics Threads Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy Races and deadlocks CS 105 “Tour of the Black Holes of Computing!”

Upload: evette

Post on 30-Jan-2016

40 views

Category:

Documents


0 download

DESCRIPTION

Programming with Threads. CS 105 “Tour of the Black Holes of Computing!”. Topics Threads Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy Races and deadlocks. Traditional View of a Process. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Programming with Threads

Programming with Threads

Topics Threads Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy Races and deadlocks

CS 105“Tour of the Black Holes of Computing!”

Page 2: Programming with Threads

– 2 – CS 105

Traditional View of a Process

Process = process context + code, data, and stack

shared libraries

run-time heap

0

read/write data

Program context: Data registers Condition codes Stack pointer (SP) Program counter (PC)Kernel context: VM structures File descriptor table brk pointer

Code, data, and stack

read-only code/data

stackSP

PC

brk

Process context

Page 3: Programming with Threads

– 3 – CS 105

Alternate View of a Process

Process = thread + code, data, and kernel context

shared libraries

run-time heap

0

read/write dataThread context: Data registers Condition codes Stack pointer (SP) Program counter (PC)

Code and Data

read-only code/data

stackSP

PC

brk

Thread (main thread)

Kernel context: VM structures File descriptor table brk pointer

Page 4: Programming with Threads

– 4 – CS 105

A Process With Multiple ThreadsMultiple 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)

shared libraries

run-time heap

0

read/write dataThread 1 context: Data registers Condition codes SP1 PC1

Shared code and data

read-only code/data

stack 1

Thread 1 (main thread)

Kernel context: VM structures File descriptor table brk pointer

Thread 2 context: Data registers Condition codes SP2 PC2

stack 2

Thread 2 (peer thread)

Page 5: Programming with Threads

– 5 – CS 105

Logical View of Threads

Threads associated with a process form pool of peers Unlike processes, which form tree hierarchy

P0

P1

sh sh sh

foo

bar

T1

Process hierarchyThreads associated with process foo

T2T4

T5 T3

shared code, dataand kernel context

Page 6: Programming with Threads

– 6 – CS 105

Concurrent Thread Execution

Two threads run concurrently (are concurrent) if their logical flows overlap in time

Otherwise, they are sequential (same rule as for processes)

Examples: Concurrent: A & B, A&C Sequential: B & C

Time

Thread A Thread B Thread C

Page 7: Programming with Threads

– 7 – 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

How threads and processes are different Threads share code and data, processes (typically) do not Threads are somewhat cheaper than processes

Process control (creating and reaping) is twice as expensive as thread control

Linux/Pentium III numbers:

» ~20K cycles to create and reap a process

» ~10K cycles to create and reap a thread

Page 8: Programming with Threads

– 8 – CS 105

Posix Threads (Pthreads) InterfacePthreads: 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_exitexit [terminates all threads] , return [terminates current

thread]

Synchronizing access to shared variablespthread_mutex_init, pthread_mutex_[un]lockpthread_cond_init, pthread_cond_[timed]wait

Page 9: Programming with Threads

– 9 – CS 105

The Pthreads "hello, world" Program

/* * hello.c - Pthreads "hello, world" program */#include "csapp.h"

void *howdy(void *vargp);

int main() { pthread_t tid;

Pthread_create(&tid, NULL, howdy, NULL); Pthread_join(tid, NULL); exit(0);}

/* thread routine */void *howdy(void *vargp) { printf("Hello, world!\n"); return NULL;}

Thread attributes (usually NULL)

Thread arguments(void *p)

return value(void **p)

Page 10: Programming with Threads

– 10 – CS 105

Execution of Threaded “hello, world”

main thread

peer thread

return NULL;main thread waits for peer thread to terminate

exit() terminates

main thread and any peer threads

call Pthread_create()

call Pthread_join()

Pthread_join() returns

printf()

(peer threadterminates)

Pthread_create() returns

Page 11: Programming with Threads

– 11 – CS 105

Pros and Consof Thread-Based Designs

+ Threads take advantage of multicore/multi-CPU H/W

+ Easy to share data structures between threads E.g., logging information, file cache

+ Threads are more efficient than processes

– Unintentional sharing can introduce subtle and hard-to-reproduce errors! Ease of data sharing is greatest strength of threads Also greatest weakness!

Page 12: Programming with Threads

– 12 – CS 105

Shared Variables in Threaded C ProgramsQuestion: Which variables in a threaded C program are

shared variables? Answer not as simple as “global variables are shared” and

“stack variables are private”

Requires answers to the following questions: What is the memory model for threads? How are variables mapped to memory instances? How many threads reference each of these instances?

Page 13: Programming with Threads

– 13 – CS 105

Threads Memory ModelConceptual model:

Each thread runs in larger 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

Operationally, this model is not strictly enforced: Register values are truly separate and protected But any thread can read and write the stack of any other thread

Mismatch between conceptual and operational model is a source of confusion and errors

Page 14: Programming with Threads

– 14 – CS 105

Example of Threads Accessing Another Thread’s Stack

char **ptr; /* global */

int main(){ int i; pthread_t tid; char *msgs[N] = { "Hello from foo", "Hello from bar" }; ptr = msgs; for (i = 0; i < 2; i++) Pthread_create(&tid, NULL, thread, (void *)i); Pthread_exit(NULL);}

/* thread routine */void *thread(void *vargp){ int myid = (int)vargp; static int svar = 0; printf("[%d]: %s (svar=%d)\n", myid, ptr[myid], ++svar);}

Peer threads access main thread’s stackindirectly through global ptr variable

Page 15: Programming with Threads

– 15 – CS 105

Mapping Vars to Mem. Instances

char **ptr; /* global */

int main(){ int i; pthread_t tid; char *msgs[N] = { "Hello from foo", "Hello from bar" }; ptr = msgs; for (i = 0; i < 2; i++) Pthread_create(&tid, NULL, thread, (void *)i); Pthread_exit(NULL);}

/* thread routine */void *thread(void *vargp){ int myid = (int)vargp; static int svar = 0; printf("[%d]: %s (svar=%d)\n", myid, ptr[myid], ++svar);}

Global var: 1 instance (ptr [data])

Local static var: 1 instance: svar [data]

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

Local automatic var: 2 instances: myid.p0[peer thread 0’s stack], myid.p1[peer thread 1’s stack]

Page 16: Programming with Threads

– 16 – CS 105

Shared Variable Analysis

Which variables are shared?

Variable Referenced by Referenced by Referenced byinstance main thread? peer thread 0? peer thread 1?

ptr yes yes yessvar no yes yesi.m yes no nomsgs.m yes yes yesmyid.p0 no yes nomyid.p1 no no yes

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.

Page 17: Programming with Threads

– 17 – CS 105

badcnt.c: An Improperly Synchronized Threaded Programunsigned int cnt = 0; /* shared */

int main() { pthread_t tid1, tid2; Pthread_create(&tid1, NULL, count, NULL); Pthread_create(&tid2, NULL, count, NULL);

Pthread_join(tid1, NULL); Pthread_join(tid2, NULL);

if (cnt != (unsigned)NITERS*2) printf("BOOM! cnt=%d\n", cnt); else printf("OK cnt=%d\n", cnt);}

/* thread routine */void *count(void *arg) { int i; for (i=0; i<NITERS; i++) cnt++; return NULL;}

linux> ./badcntBOOM! cnt=198841183

linux> ./badcntBOOM! cnt=198261801

linux> ./badcntBOOM! cnt=198269672

cnt should be200,000,000.

What went wrong?!

Page 18: Programming with Threads

– 18 – CS 105

Assembly Code for Counter Loop

.L9:movl -4(%ebp),%eaxcmpl $99999999,%eaxjle .L12jmp .L10

.L12:movl cnt,%eax # Loadleal 1(%eax),%edx # Updatemovl %edx,cnt # Store

.L11:movl -4(%ebp),%eaxleal 1(%eax),%edxmovl %edx,-4(%ebp)jmp .L9

.L10:

Corresponding asm codefor (i=0; i<NITERS; i++) cnt++;

C code for counter loop

Head (Hi)

Tail (Ti)

Load cnt (Li)Update cnt (Ui)

Store cnt (Si)

Page 19: Programming with Threads

– 19 – CS 105

Concurrent ExecutionKey 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

H1

L1

U1

S1

H2

L2

U2

S2

T2

T1

1111222221

-011-----1

0001111222

i (thread) instri cnt%eax1

OK

-----1222-

%eax2

Page 20: Programming with Threads

– 20 – 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

Page 21: Programming with Threads

– 21 – CS 105

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

1112211222

-01--11---

0000011111

i (thread) instri cnt%eax1

----0--111

%eax2

Oops!

Page 22: Programming with Threads

– 22 – CS 105

Concurrent Execution (cont)

How about this ordering?

H1

L1

H2

L2

U2

S2

U1

S1

T1

T2

1122221112

i (thread) instri cnt%eax1 %eax2

We can clarify our understanding of concurrentexecution with the help of the progress graph

Page 23: Programming with Threads

– 23 – CS 105

Progress GraphsProgress graph depictsdiscrete execution state space of concurrentthreads

Each axis corresponds tosequential order ofinstructions in a thread

Each point corresponds toa possible execution state(Inst1, Inst2)

E.g., (L1, S2) denotes statewhere thread 1 hascompleted L1 and thread2 has completed S2

H1 L1 U1 S1 T1

H2

L2

U2

S2

T2

Thread 1

Thread 2

(L1, S2)

Page 24: Programming with Threads

– 24 – CS 105

Trajectories in Progress Graphs

Trajectory is sequence of legal state transitions that describes one possible concurrent execution ofthe 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

Page 25: Programming with Threads

– 25 – CS 105

Critical Sections and Unsafe Regions

L, U, and S form a critical section withrespect to the sharedvariable cnt

Instructions in criticalsections (w.r.t. to someshared variable) should not be interleaved

Sets of states where suchinterleaving occursform unsafe regions

H1 L1 U1 S1 T1

H2

L2

U2

S2

T2

Thread 1

Thread 2

Unsafe region

critical section wrt cnt

critical section wrt cnt

Page 26: Programming with Threads

– 26 – CS 105

Safe and Unsafe Trajectories

Def: A trajectory is safe iff it doesn’t enter any part of an unsafe region

Claim: A trajectory is correct (wrt cnt) iff it issafe

H1 L1 U1 S1 T1

H2

L2

U2

S2

T2

Thread 1

Thread 2

Unsafe region Unsafetrajectory

Safe trajectory

critical section wrt cnt

critical section wrt cnt

Page 27: Programming with Threads

– 27 – 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;}

Page 28: Programming with Threads

– 28 – 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 sWhen while loop in process X terminates, only that process has

decremented s

Semaphore invariant: (s >= 0)

Page 29: Programming with Threads

– 29 – CS 105

Safe Sharing with Semaphores

Here is how we would use P and V operations to synchronize the threads that update cnt

/* Semaphore s is initially 1 */

/* Thread routine */void *count(void *arg){ int i;

for (i=0; i<NITERS; i++) { P(s); cnt++; V(s); } return NULL;}

Page 30: Programming with Threads

– 30 – CS 105

Safe Sharing With Semaphores

Provide mutually exclusive access to shared variable by surrounding critical section with P and V operations on semaphores (initially set to 1)

Semaphore invariant creates forbidden regionthat encloses unsafe region and is never touched by any trajectory

H1 P(s) V(s) T1

Thread 1

Thread 2

L1 U1 S1

H2

P(s)

V(s)

T2

L2

U2

S2

Unsafe region

Forbidden region

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

Initiallys = 1

Page 31: Programming with Threads

– 31 – CS 105

deadlockregion

Deadlock

P(s) V(s)

V(t)

Thread 1

Thread 2

Initially, s=t=1

P(t)

P(t) V(t)

forbiddenregion for s

forbiddenregion for t

P(s)

V(s) deadlockstate

Locking introducespotential for deadlock: waiting for a condition that will never be true.

Any trajectory that entersdeadlock region willeventually reachdeadlock state, waiting for either s or t to become nonzero.

Other trajectories luck out and skirt deadlock region.

Unfortunate fact: deadlock is often non-deterministic (thus hard to detect).

Page 32: Programming with Threads

– 32 – 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");}

/* P operation on semaphore sem */void P(sem_t *sem) { if (sem_wait(sem) == -1) unix_error("P");}

/* V operation on semaphore sem */void V(sem_t *sem) { if (sem_post(sem) == -1) unix_error("V");}

Page 33: Programming with Threads

– 33 – CS 105

Sharing With POSIX Semaphores/* goodcnt.c - properly sync’dcounter program */#include "csapp.h"#define NITERS 10000000

unsigned int cnt; /* counter */sem_t sem; /* semaphore */

int main() { pthread_t tid1, tid2;

Sem_init(&sem, 0, 1); /* sem=1 */

/* create 2 threads and wait */ ...

if (cnt != (unsigned)NITERS*2) printf("BOOM! cnt=%d\n", cnt); else printf("OK cnt=%d\n", cnt); exit(0);}

/* thread routine */void *count(void *arg){ int i;

for (i=0; i<NITERS; i++) { P(&sem); cnt++; V(&sem); } return NULL;}

Page 34: Programming with Threads

– 34 – CS 105

Signaling With Semaphores

Common synchronization pattern: Producer waits for slot, inserts item in buffer, and “signals” consumer Consumer waits for item, removes it from buffer, and “signals”

producer “Signals” in this context has nothing to do with Unix signals

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

producerthread

sharedbuffer

consumerthread

Page 35: Programming with Threads

– 35 – CS 105

Producer-Consumer on Buffer That Holds One Item

/* buf1.c - producer-consumeron 1-element buffer */#include “csapp.h”

#define NITERS 5

void *producer(void *arg);void *consumer(void *arg);

struct { int buf; /* shared var */ sem_t full; /* sems */ sem_t empty;} shared;

int main() { pthread_t tid_producer; pthread_t tid_consumer;

/* initialize the semaphores */ Sem_init(&shared.empty, 0, 1); Sem_init(&shared.full, 0, 0);

/* create threads and wait */ Pthread_create(&tid_producer, NULL, producer, NULL); Pthread_create(&tid_consumer, NULL, consumer, NULL); Pthread_join(tid_producer, NULL); Pthread_join(tid_consumer, NULL); exit(0);}

Page 36: Programming with Threads

– 36 – CS 105

Producer-Consumer (cont)

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

for (i=0; i<NITERS; i++) { /* produce item */ item = i; printf("produced %d\n", item);

/* write item to buf */ P(&shared.empty); shared.buf = item; V(&shared.full); } return NULL;}

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

for (i=0; i<NITERS; i++) { /* read item from buf */ P(&shared.full); item = shared.buf; V(&shared.empty);

/* consume item */ printf("consumed %d\n", item); } return NULL;}

Initially: empty = 1, full = 0.

Page 37: Programming with Threads

– 37 – CS 105

Thread Safety

Functions called from a thread must be thread-safe

We identify four (non-disjoint) classes of thread-unsafe functions: Class 1: Failing to protect shared variables Class 2: Relying on persistent state across invocations Class 3: Returning pointer to static variable Class 4: Calling thread-unsafe functions

Page 38: Programming with Threads

– 38 – 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

Page 39: Programming with Threads

– 39 – 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

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

Page 40: Programming with Threads

– 40 – CS 105

Thread-Unsafe Functions (cont)Class 3: Returning pointer

to static variable

Fixes: 1. Rewrite code so caller

passes pointer to struct» Issue: Requires

changes in caller and callee

2. Lock-and-copy» Issue: Requires only

simple changes in caller (and none in callee)

» However, caller must free memory

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

struct hostent *gethostbyname(char* name){ static struct hostent h; <contact DNS and fill in h> return &h;}

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

Page 41: Programming with Threads

– 41 – CS 105

Thread-Unsafe Functions

Class 4: Calling thread-unsafe functions Calling one thread-unsafe function makes an entire function

thread-unsafe

Fix: Modify the function so it calls only thread-safe functions

Page 42: Programming with Threads

– 42 – CS 105

Reentrant FunctionsA 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

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)

Reentrantfunctions

All functions

Thread-unsafefunctions

Thread-safefunctions

Page 43: Programming with Threads

– 43 – 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

All Unix system calls are thread-safe

Library calls that aren’t thread-safe:

Thread-unsafe function Class Reentrant versionasctime 3 asctime_rctime 3 ctime_rgethostbyaddr 3 gethostbyaddr_rgethostbyname 3 gethostbyname_rinet_ntoa 3 (none)localtime 3 localtime_rrand 2 rand_r

Page 44: Programming with Threads

– 44 – CS 105

Threads Summary

Threads provide another mechanism for writing concurrent programs

Threads are growing in popularity Somewhat cheaper than processes Easy to share data between threads

However, the ease of sharing has a cost: Easy to introduce subtle synchronization errors Tread carefully with threads!

For more info: D. Butenhof, “Programming with Posix Threads”, Addison-

Wesley, 1997