59
Programming with Threads Ref: • “Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron, First edition, Pearson Education 2003 Topics Shared variables The need for synchronization Synchronizing with semaphores Thread safety and reentrancy Races and deadlocks

Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

  • Upload
    others

  • View
    6

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

Programming with Threads

Ref: • “Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron, First edition, Pearson Education 2003

Topics

Shared variables

The need for synchronization

Synchronizing with semaphores

Thread safety and reentrancy

Races and deadlocks

Page 2: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 2 –

Shared Variables in Threaded C Programs

Question: Which variables in a threaded C program are shared variables?

The answer is not as simple as “global variables are shared”

and “stack variables are private”.

We must understand what is shared and what is not shared.

Requires answers to the following questions:

What is the memory model for threads?

How are instances of the variables mapped to memory?

How many threads reference each of these instances?

The variable is shared if and only if multiple threads reference some of instance of the variable.

Page 3: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 3 –

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 the remaining process context.

Code, R/W data, heap, and shared library code

and data areas (segments of the process virtual

address space.)

Open files and installed handlers

Page 4: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 4 –

Threads Memory Model (cont)

Operationally, this model is not strictly enforced:

While register values are truly separate and protected....

It is impossible for one thread to read or write the register

values of another thread

Any thread can access any location in the shared virtual

memory

- read and write the stack of any other thread

- If one thread modifies a memory location, then every other

thread will see the change, if it reads that location.

Registers are never shared, virtual memory is always shared.

Mismatch between the conceptual and operation model is a source of confusion and errors.

Page 5: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 5 –

Example of Threads Accessing Another Thread’s Stack#include "csapp.h"

#define N 2

void *thread(void *vargp);

char **ptr; /* global */

int main()

{

int i;

pthread_t tid;

char *msgs[N] = {

"Hello from Block - A",

"Hello from Block - B"

};

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 cnt = 0;

printf("[%d]: %s (cnt=%d)\n",

myid, ptr[myid], ++cnt);

}

Peer threads access main thread’s stack

indirectly through global ptr variable

Page 6: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 6 –

[sanjay@dslabsrv17 conc]$ ./sharing

[0]: Hello from Block - A (cnt=1)

[1]: Hello from Block - B (cnt=2)

[sanjay@dslabsrv17 conc]$

Thread Stacks:

• Contained in the stack area of the virtual

address space, and are usually accessed

independently of their respective threads.

• Different thread stacks are not protected from

other threads, i.e. if a thread can somehow

manage to acquire a pointer to another

thread’s stack, then it can read and write any

part of that stack.

Page 7: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 7 –

Mapping Variables to Memory Variables

Global Variables

• Any variable declared outside of a function.

• At run time, the read/write area of virtual memory contains exactly one instance of each global variable that can be referenced by any thread.

• For example, the global ptr variable has one run-time instance in the R/W area of virtual memory.

• When there is only one instance of a variable, we will denote the instance by simply using the variable name, e.g. ptr

Page 8: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 8 –

Mapping Variables to Memory Variables (cont)

Local automatic variables

• A local automatic variable is one that is declared inside a function without the static attribute.

• At run time, each thread’s stack contains its own instances of any local automatic variables. It is true even if multiple threads execute the same thread routine.

For example,

• There is one instance of the local variable tid, and it resides on

the stack of the main thread. We will denote this instance as tid.m.

• There are two instances of the local variable myid, one instance

on the stack of peer thread 0 and the other on the stack of peer thread 1. We will denote these instances as myid.p0 and myid.p1, respectively.

Page 9: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 9 –

Mapping Variables to Memory Variables (cont)

Local Static Variables

• A local static variable is one that is declared inside a function with the static attribute.

• As with global variables, R/W area of virtual memory contains exactly one instance of each local static variable declared in the program.

• For example, even though each peer thread in our program declares cnt in line 25, at run time there is only one instance of cnt residing in the R/W area of

virtual memory. Each peer thread reads and writes this instance.

Page 10: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 10 –

Shared Variables

• A variable v is shared if and only if one of its instances is referenced by more than one thread.

For example,

• Variable cnt is shared because it has one run-time

instance, and this instance is referenced in both peer threads.

• Variable myid is not shared because each of its two

instances is referenced by exactly on thread.

• It is important to note that local automatic variables such as msgs can also be shared.

Page 11: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 11 –

Mapping Variables to Mem. Instances

char **ptr; /* global */

int main()

{

int i;

pthread_t tid;

char *msgs[N] = {

"Hello from Block - A",

"Hello from Block - B"

};

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 cnt = 0;

printf("[%d]: %s (cnt=%d)\n",

myid, ptr[myid], ++cnt);

}

Global var: 1 instance (ptr [data])

Local static var: 1 instance (cnt [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 12: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 12 –

Shared Variable Analysis

Which variables are shared?

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

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.

Page 13: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 13 –

badcnt.c: An Improperly Synchronized Threaded Program#include "csapp.h“

#define NITERS 200000000

void *count(void *arg);

unsigned 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(“Value of cnt variable not

proper!! 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;

}

Page 14: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 14 –

[sanjay@dslabsrv17 conc]$ ./badcnt

Value of cnt variable not proper! cnt=242529250

[sanjay@dslabsrv17 conc]$ ./badcnt

Value of cnt variable not proper! cnt=269131504

[sanjay@dslabsrv17 conc]$ ./badcnt

Value of cnt variable not proper! cnt=251801434

[sanjay@dslabsrv17 conc]$

cnt should be equal to 200,000,000.

What went wrong?!

Page 15: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 15 –

Assembly Code for Counter Loop

.L9:

movl -4(%ebp),%eax

cmpl $99999999,%eax

jle .L12

jmp .L10

.L12:

movl cnt,%eax # Load

leal 1(%eax),%edx # Update

movl %edx,cnt # Store

.L11:

movl -4(%ebp),%eax

leal 1(%eax),%edx

movl %edx,-4(%ebp)

jmp .L9

.L10:

Corresponding asm code(gcc -O0 -fforce-mem)

for (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 16: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 16 –

Concurrent Execution

Five parts of loop code:

Hi: The block of instructions at the head of the loop.

Li: The instruction that loads the shared variable cnt

into register %eaxi, where %eax, denotes the value of register %eax in thread i.

Ui: The instruction that updates (increments) %eaxi.

Si: The instruction that stores the updated value of %eaxi back to the shared variable cnt.

Ti: the block of instructions at the tail of the loop.

Page 17: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 17 –

Concurrent Execution (cont)

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

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%eax1

Correct Ordering

-

-

-

-

-

1

2

2

2

-

%eax2

Page 18: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 18 –

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%eax1

-

-

-

-

0

-

-

1

1

1

%eax2

Incorrect Ordering

Page 19: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 19 –

Concurrent Execution (cont)

How about this ordering?

H1

L1

H2

L2

U2

S2

U1

S1

T1

T2

1

1

2

2

2

2

1

1

1

2

i (thread) instri cnt%eax1 %eax2

We can clarify our understanding of concurrent

execution with the help of the progress graph

Page 20: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 20 –

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.H1 L1 U1 S1 T1

H2

L2

U2

S2

T2

Thread 1

Thread 2

(L1, S2)

Page 21: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 21 –

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

Page 22: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 22 –

Critical Sections and Unsafe Regions

L, U, and S form a

critical section with

respect to the sharedvariable cnt.

Instructions in critical

sections (wrt to 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

Unsafe region

critical section wrt cnt

critical

section wrt cnt

Page 23: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 23 –

Safe and Unsafe Trajectories

Def: A trajectory is safe

iff it doesn’t touch any

part of an unsafe region.

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

safe.

H1 L1 U1 S1 T1

H2

L2

U2

S2

T2

Thread 1

Thread 2

Unsafe region Unsafe

trajectory

Safe trajectory

critical section wrt cnt

critical

section wrt cnt

Page 24: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 24 –

Semaphores

Question: How can we guarantee a safe trajectory?

We must synchronize the threads so that they never enter an unsafe state.

Classic solution: Dijkstra's P and V operations on semaphores.

semaphore: non-negative integer synchronization variable. P(s): [ while (s == 0) wait(); s--; ]

» Dutch for "Proberen" (test)

V(s): [ s++; ]

» Dutch for "Verhogen" (increment)

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 P terminates, only that P can decrement s.

Semaphore invariant: (s >= 0)

Page 25: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 25 –

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 26: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 26 –

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 a forbidden region

that 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

Initially

s = 1

Page 27: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 27 –

POSIX Semaphores

#include <semaphore.h>

Note: Refer /usr/include/semaphore.h

int sem_init(sem_t *sem, o, unsinged int value);

int sem_wait(sem_t *s); /* P(s) */

int sem_post(sem_t *s); /* V(s) */

• P and V Operations will be performed by calling the sem_wait and sem_postfunctions respectively.

• Following wrapper functions are defined:

#include “csapp.h”

void P(sem_t *s); /Wrapper function for sem_wait */

void S(sem_t *s); /Wrapper function for sem_post */

Page 28: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 28 –

POSIX Semaphores (cont)

/* 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");

Page 29: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 29 –

Sharing With POSIX Semaphores/*

* goodcnt.c - A properly synchronized counter program

*/

/* $begin goodcnt */

#include "csapp.h"

#define NITERS 10000000

void *count(void *arg);

/* shared variables */

unsigned int cnt; /* counter */

sem_t mutex; /* semaphore that protects counter */

int main()

{ pthread_t tid1, tid2;

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

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("Value of cnt variable not proper !! %d\n", cnt);

else

printf("OK cnt=%d\n", cnt);

exit(0); }

Page 30: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 30 –

Sharing With POSIX Semaphores (cont)

/* thread routine */

void *count(void *arg)

{

int i;

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

P(&mutex);

cnt++;

V(&mutex);

}

return NULL;

}

Page 31: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 31 –

[sanjay@dslabsrv17 conc]$ ./goodcnt

OK cnt=20000000

[sanjay@dslabsrv17 conc]$ ./goodcnt

OK cnt=20000000

[sanjay@dslabsrv17 conc]$ ./goodcnt

OK cnt=20000000

Page 32: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 32 –

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 keyboard hits and

inserts corresponding events in buffer.

Consumer retrieves events from buffer and paints the display.

producer

thread

shared

bufferconsumer

thread

Page 33: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 33 –

Using semaphores to Schedule Shared Resources

• SBUF package: to build producer-consumer programs. It manipulates buffers of type sbuf_t

• Items are stored in a dynamically allocated integer array (buf) with n items.

• front and rear indices keep track of the first and last items in the array.

• Three semaphores control synchronize access to the buffer.

• mutex semaphore provides mutually exclusive buffer access.

• slots and items semaphores count the number of empty slots and available items respectively.

Page 34: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 34 –

Using semaphores to Schedule Shared Resources

• The sbuf_init function allocates heap memory for the buffer, sets front and rear to indicate an empty buffer, and assigns initial values to the three semaphores

• The sbuf_deinit function frees the buffer storage when the application is completed

• The sbuf_insert function waits for an available slot, locks the mutex, adds the item, unlock the mutex and the announces the availability of a new item.

• The sbuf_remove function is symmetric. After waiting for an available buffer item, it locks the mutex, removes the item from the front of the buffer, unlocks mutex, and then signal the availability of a new slot.

Page 35: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 35 –

Using semaphores to Schedule Shared Resources

#ifndef __SBUF_H__

#define __SBUF_H__

#include "csapp.h“

/* $begin sbuft Shared Buffer */

typedef struct {

int *buf; /* Buffer array */

int n; /* Maximum number of slots */

int front; /* buf[(front+1)%n] is first item */

int rear; /* buf[rear%n] is last item */

sem_t mutex; /* Protects accesses to buf */

sem_t slots; /* Counts available slots */

sem_t items; /* Counts available items */

} sbuf_t;

/* $end sbuft */

void sbuf_init(sbuf_t *sp, int n);

void sbuf_deinit(sbuf_t *sp);

void sbuf_insert(sbuf_t *sp, int item);

int sbuf_remove(sbuf_t *sp);

#endif /* __SBUF_H__ */

Page 36: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 36 –

/* sbuf.c

$begin sbufc */

#include "csapp.h"

#include "sbuf.h"

/* Create an empty, bounded, shared FIFO buffer with n slots */

/* $begin sbuf_init */

void sbuf_init(sbuf_t *sp, int n)

{ sp->buf = Calloc(n, sizeof(int));

sp->n = n; /* Buffer holds max of n items */

sp->front = sp->rear = 0; /* Empty buffer iff front == rear */

Sem_init(&sp->mutex, 0, 1); /* Binary semaphore for locking */

Sem_init(&sp->slots, 0, n); /* Initially, buf has n empty slots */

Sem_init(&sp->items, 0, 0); /* Initially, buf has zero data items

*/

}

/* $end sbuf_init */

/* Clean up buffer sp */

/* $begin sbuf_deinit */

void sbuf_deinit(sbuf_t *sp)

{

Free(sp->buf);

}

/* $end sbuf_deinit */

Page 37: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 37 –

/* Insert item onto the rear of shared buffer sp */

/* $begin sbuf_insert */

void sbuf_insert(sbuf_t *sp, int item)

{

P(&sp->slots); /* Wait for available slot */

P(&sp->mutex); /* Lock the buffer */

sp->buf[(++sp->rear)%(sp->n)] = item; /* Insert the item */

V(&sp->mutex); /* Unlock the buffer */

V(&sp->items); /* Announce available item */

}

/* $end sbuf_insert */

/* Remove and return the first item from buffer sp */

/* $begin sbuf_remove */

int sbuf_remove(sbuf_t *sp)

{

int item;

P(&sp->items); /* Wait for available item */

P(&sp->mutex); /* Lock the buffer */

item = sp->buf[(++sp->front)%(sp->n)]; /* Remove the item */

V(&sp->mutex); /* Unlock the buffer */

V(&sp->slots); /* Announce available slot */

return item;

}

/* $end sbuf_remove */

/* $end sbufc */

Page 38: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 38 –

Concurrent server based on Prethreading

• Concurrent server: creating a new thread for each new client.

• DisAdv: Nontrivial cost of creating a new thread for each

new client

• Prethreaded Concurrent Server

• Server consists of main thread and a set of worker threads

• Main thread repeatedly accepts connection requests from

clients and places the resulting connected descriptors in a

shared buffer.

• Each worker thread repeatedly removes a descriptors from

the buffer, services the client and then waits for the next

descriptors

• Refer: echoservert_pre.c and echo_cnt.c

Page 39: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 39 –

Organization of a prethreaded concurent server:

A set of existing threads repeatedly remove and process

connected descriptions from a shared buffer

Page 40: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 40 –

Concurrent server based on Prethreading(cont)

• SBUF package is used to implement a prethreadedconcurrent server echo server

• Initialize buffer sbuf

• Main thread creates the set of worker threads

• Enters the infinite loop, accepting connection requests and inserting resulting connectioneddescriptors in sbuf.

• Each worker thread waits until it is able to remove a connected descriptor from the buffer and calls the echo_cnt function to eacho client input

• Accesses to the shared byte_cnt variable are protected by P and V operations.

Page 41: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 41 –

Event-driven programs based on threads

Concurrent prethreaded server can be an event-driven server with simple state machines for the main and worker thread.

Main thread

Two states: “Waiting for connection request” and

“Waiting for available buffer slot”

Two I/O events: “Connection request arrives” and

“Buffer slot becomes available”

Two transitions: “Accept connection request” and

“Insert buffer item”

Worker thread

One state: “Waiting for available buffer slot”

One I/O event: “Buffer slot becomes available”

One transition: “Remove buffer item”

Page 42: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 42 –

Producer-Consumer on a Buffer That Holds One Item

/* buf1.c - producer-consumer

on 1-element buffer */

#include “csapp.h”

#define NITERS 5

void *producer(void *arg);

void *consumer(void *arg);

struct {

int buf; /* shared var */

sem_t full; /* sems */

sem_t empty;

} shared;

int main() {

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 43: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 43 –

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 44: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 44 –

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 a pointer to a static variable.

Class 4: Calling thread-unsafe functions.

Page 45: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 45 –

Thread-Unsafe Functions

Class 1: Failing to protect shared variables.

Relatively easy to make thread-safe

Fix: Use P and V semaphore operations.

Issue: Synchronization operations will slow down code.

Example: goodcnt.c

Page 46: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 46 –

Thread-Unsafe Functions (cont)Class 2: Relying on persistent state across multiple

function invocations.

rand(): repeatedly from from single thread, same sequence of

numbers, not if multiple threads are calling it

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

Thread-unsafe pseudorandom number generator

Page 47: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 47 –

Thread-Unsafe Functions (cont)

Class 3: Returning a ptr to a static variable.

Such function from concurrent threads, results used by one thread will be overwritten by another thread

Fixes:

1. Rewrite code so caller passes pointer to struct.

» Issue: Requires

changes in caller and

callee.

2. Lock-and-copy

Each call lock mutex, call thread-unsafe function, dynamically allocate memory, copy result and then unlock

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;

}

Thread-safe wrapper for gethostnamebyname() uses

the lock-and-copy technique to call a Class 2 thread-unsafe function

Page 48: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 48 –

Thread-Unsafe Functions

Class 4: Calling thread-unsafe functions.

Calling one thread-unsafe function makes an entire function

thread-unsafe.

If a function f calls a thread-unsafe function g, is f thread-

unsafe?

If g is a class 2 function that relies on state across multiple

invocations, then f is also thread-unsafe and there is no

recourse of rewriting g.

If g is a class 1 or 3 function, then f can still be thread-safe it

your protect the call site and any resulting shared data with a

mutex.

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

We used lock-and-copy to write a thread-safe function that calls

a thread-unsafe function (previous slide)

Page 49: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 49 –

Thread-Safe Library Functions

All functions in the Standard C Library (at the back of your K&R text) are thread-safe.

Examples: malloc, free, printf, scanf

Most Unix system calls are thread-safe, with a few exceptions:

Thread-unsafe function Class Reentrant versionasctime 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

Page 50: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 50 –

Reentrant FunctionsA function is reentrant iff it accesses NO shared variables when

called from multiple threads.

Do not reference any shared data when called by multiple threads

Reentrant functions are a proper subset of the set of thread-safe functions.

• The set of reentrant functions if a proper subset of the thread-safe functions.

• NOTE: The fixes to Class 2 and 3 thread-unsafe functions require modifying the function to make it reentrant.

Reentrant

functions

All functions

Thread-unsafe

functions

Thread-safe

functions

Page 51: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 51 –

Reentrant Functions (cont)

• Example of reentrant version of the rand function. Here static next variable is replaced with a pointer that is passed in by the caller

int rand_r(unsigned int *nextp)

{ *nextp = *nextp * 1103515245 + 12345;

return (unsigned int) (*nextp / 65536) % 32768; }

• Is it possible to inspect the code of a function and declare a priori that it is reentrant?... It depends!

• If all function arguments are passed

• by value (e.g. no pointers) and

• all data references are to local automatic stack variables (i.e.

no references to static or global variables) then the function

is explicitly reentrant, we can assert its reentrancy

regardless of how it is called.

Page 52: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 52 –

Reentrant Functions (cont)• If we allow some parameters in explicitly reentrant function to be

passed by reference (allow them to pass pointers) then we have an implicitly reentrant function;

• in the sense that it is only reentrant if the calling threads are

careful to pass pointers to nonshared data, e.g. rand_r()

• It is important to realize that reentrancy is sometimes a property of both the caller and the callee, and not only the callee alone.

• Thread-unsafe functions listed earlier are of the class 3 variety and return a pointer to a static variable.

• Use lock-and-copy approach, but it will slow down the

execution of a program

• Lock-and-copy will not work for Class 2 thread-unsafe

functions such as rand() which relies on static state across

calls

• Unix systems provide reentrant versions of most thread-

unsafe functions, such functions end with _r suffix. These

functions are poorly documented.

Page 53: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 53 –

Races

A race occurs when the correctness of the program depends on one thread reaching point x before another thread reaches point y.

Races usually occur because programmers assume that threads will take some particular trajectory through the execution state space, forgetting the golden rule that threaded programs must work correctly for any feasible trajectory.

race.c: the main thread creates four peer threads and passes pointer to a unique integer ID to each one. Each peer thread copies the ID passed in its argument to a local variable, and then prints a message containing the ID.

Page 54: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 54 –

Races (cont)

race.c:

/* 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 55: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 55 –

Races (cont)

• The problem is caused by a race between each peer thread and the main thread.

• When the main thread creates a peer thread:Pthread_create(&tid[i], NULL, thread, &i);

• It passes a pointer to the local stack variable i.

• At this point the race is between the next call to Pthread_create and the deferencing and

assignment of the argument in line :int myid = *((int *)vargp);

• If peer thread executes above code before main thread creates next peer thread, then myid variable will get correct ID. Otherwise it will contain ID of some other thread.

Page 56: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 56 –

Races (cont)

• Correct answer depends on how the kernel schedules the execution of threads. It may differ from system to system.

Solution:

• We can dynamically allocate a separate block for each integer ID and pass the thread routine a pointer to this block:

for (i = 0; i < N; i++) { ptr = Malloc(sizeof(int));

*ptr = i;

Pthread_create(&tid[i], NULL, thread, ptr); }

….

void *thread(void *vargp) { int myid = *((int *)vargp);

Free(vargp); }

• Note: Do not forget to free the block in order to avoid memory leak.

Page 57: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 57 –

deadlock

region

Deadlock

P(s) V(s)

V(t)

Thread 1

Thread 2

Initially, s=t=1

P(t)

P(t) V(t)

forbidden

region for s

forbidden

region for t

P(s)

V(s) deadlock

state

Locking introduces the

potential for deadlock:

waiting for a condition that

will never be true.

Any trajectory that enters

the deadlock region will

eventually reach the

deadlock state, waiting for either s or t to become

nonzero.

Other trajectories luck out

and skirt the deadlock

region.

Unfortunate fact: deadlock

is often non-deterministic.

Page 58: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 58 –

Deadlock (cont)

• When binary semaphores are used, following rule can be applied:

• Mutex lock ordering rule: A program is deadlock-free if, for each pair of mutexes (s, t) in the program, each thread that holds both s and t simultaneously locks them in the same order.

• Lock s first, then t in each thread.

Page 59: Programming with Threadscourses.daiict.ac.in/pluginfile.php/5096/mod_resource/...Programming with Threads Ref: •“Computer Systems: A Programmer's Perspective”, Bryant and O'Hallaron,

– 59 –

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.