Threads Programming Thread creation Synchronization.

33
Threads Programming Thread creation Synchronization

Transcript of Threads Programming Thread creation Synchronization.

Page 1: Threads Programming Thread creation Synchronization.

Threads Programming

•Thread creation•Synchronization

Page 2: Threads Programming Thread creation Synchronization.

Threads vs. ProcessesCreation of a new process using fork is expensive (time & memory). IPC is required to pass information between parent and child

– A thread (sometimes called a lightweight process) does not require lots of memory or startup time.

– It shares memory with parent, no IPC is required

Page 3: Threads Programming Thread creation Synchronization.

fork()

fork()

Process A

GlobalVariables

Code

Stack

Process B

GlobalVariables

Code

Stack

Page 4: Threads Programming Thread creation Synchronization.

pthread_create()

Process AThread 1

GlobalVariables

Code

Stack

Process AThread 2

Stack

pthread_create()

Page 5: Threads Programming Thread creation Synchronization.

Thread Programming

Share heap, data, text segments, global variables, file descriptors, CWD, user, group ids, signal handlers, signal dispositions

Individual stack, registers, program counters, errno, signal mask, priority

Page 6: Threads Programming Thread creation Synchronization.

Introduction to Thread Programming

Advantages of using threads Increased efficiency Shared memory (efficient communication)

easier for monitor and control (e.g. statistics collection)

Disadvantages of using threads Synchronization overhead Many library functions are not thread-safe

(e.g., gethostbyname)

Page 7: Threads Programming Thread creation Synchronization.

Thread Programming API

Thread Management creation, detach, join, exit POSIX requires all threads are created as

joinable threads Thread Synchronization

join mutex (i.e. semapore with value of 1)

semaphore condition variables

Page 8: Threads Programming Thread creation Synchronization.

Client using threads

Page 9: Threads Programming Thread creation Synchronization.

Client.cvoid *copyto (void *);static int sockfd; /*

global for both threads to access */

static FILE *fp;voidstr_cli(FILE *fp_arg, int

sockfd_arg) { char recvline[MAXLINE]; pthread_t tid; sockfd = sockfd_arg; /*

copy arguments to externals */ fp = fp_arg; Pthread_create(&tid, NULL,

copyto, NULL); while (Readline(sockfd,

recvline, MAXLINE) > 0) Fputs(recvline, stdout);}

void * copyto(void *arg) { char sendline[MAXLINE]; while (Fgets(sendline, MAXLINE, fp) ! =

NULL) Writen(sockfd, sendline, strlen(sendline)); Shutdown(sockfd, SHUT_WR); /* EOF on

stdin, send FIN */

return (NULL); /* return (i.e., thread terminates) when

EOF on stdin */ }

Page 10: Threads Programming Thread creation Synchronization.

int main(int argc, char **argv) { int listenfd, *iptr; thread_t tid; socklen_t addrlen, len; struct sockaddr *cliaddr; listenfd = Tcp_listen(argv[1], argv[2], &addrlen);cliaddr = Malloc(addrlen); for ( ; ; ) { len = addrlen; iptr = Malloc(sizeof(int)); *iptr = Accept(listenfd, cliaddr, &len);Pthread_create(&tid, NULL, &doit, iptr); } }

TCP Echo Server using Threads

static void * doit(void *arg) { int connfd; connfd = *((int *) arg); free(arg); Pthread_detach(pthread_self()); str_echo(confd); /* same function as before */ Close(confd); /* done with connected socket */ return (NULL); }

Page 11: Threads Programming Thread creation Synchronization.

Thread Creation

initializes its attributes using the thread attributes object specified by the attr parameter

thread attribues: priority, stack size, name, detach-state, etc.

start executing start_routine, with the parameter arg

#include <pthread.h>int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void*(*start_routine) (void *), void *arg)

Page 12: Threads Programming Thread creation Synchronization.

pthread_create()

The return value is 0 for OK. Positive error number on error.

Does not set errno !!!

Thread ID is returned in tid

Page 13: Threads Programming Thread creation Synchronization.

Thread Creation• Each thread has a unique ID, a thread can find out it's

ID by calling pthread_self(). • Thread IDs are of type pthread_t which is usually an

unsigned int. When debugging, it's often useful to do something like this:

• printf("Thread %u:\n",pthread_self());• When func() is called the value arg specified in the call

to pthread_create() is passed as a parameter.• func can have only 1 parameter, and it can't be larger

than the size of a void *.

Page 14: Threads Programming Thread creation Synchronization.

Thread Creation

Complex parameters can be passed by creating a structure and passing the address of the structure.

The structure can't be a local variable (of the function calling pthread_create)!! threads have different stacks!

Page 15: Threads Programming Thread creation Synchronization.

Thread Lifecycle – joinable threadsimilar to waitpid(), calling thread blocks

Thread Termination1. returns from its starting

routing (main program) 2. call pthread_exit3. cancelled by another

thread pthread_cancel4. process terminated

status returned

thread attributes specified

Page 16: Threads Programming Thread creation Synchronization.

pthread_join

Wait for a given thread to terminate by calling pthread_join. similar to waitpid.

#include <pthread.h> int pthread_join (pthread_t tid, void **

status);

Page 17: Threads Programming Thread creation Synchronization.

Thread Detach

A thread can be changed to detached state

A detached thread releases all its resource when it terminates

A detached thread cannot be joined Example

pthread_detach(pthread_self())

#include <pthread.h>int pthread_detach (pthread_t tid)

Page 18: Threads Programming Thread creation Synchronization.

Thread-safe Functions Two of the functions are thread-safe

only if the caller allocates space for the result and passes that pointer as the argument to the function. inet_pton() //thread safe gethostbyname() //not thread safe

Page 19: Threads Programming Thread creation Synchronization.

Synchronization Problem#define NLOOP 5000 int counter;

/* incremented by threads */

void *doit(void *); int main(int argc, char **argv) { pthread_t tidA, tidB; Pthread_create(&tidA, NULL,

&doit, NULL); Pthread_create(&tidB,

NULL, &doit, NULL); /* wait for both

threads to terminate */ Pthread_join(tidA, NULL); Pthread_join(tidB, NULL); exit(0); }

void * doit(void *vptr) { int i, val; /* * Each thread fetches, prints, and

increments the counter NLOOP times.

* The value of the counter should increase monotonically.

*/ for (i = 0; i < NLOOP; i++) { val = counter; printf("%d: %d\n", pthread_self(),

val + 1); counter = val + 1; } return (NULL); }

Page 20: Threads Programming Thread creation Synchronization.

Output

Page 21: Threads Programming Thread creation Synchronization.

Mutex Variables

Protects access to shared data resources A typical sequence

Create and initialize a mutex variable Several threads attempt to lock the mutex Only one succeeds and that thread owns the

mutex The owner thread performs some set of actions The owner unlocks the mutex Another thread acquires the mutex and repeats

the process Finally the mutex is destroyed

Page 22: Threads Programming Thread creation Synchronization.

Mutex Variables#define NLOOP 5000

int counter; /* incremented by threads */

pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

void *doit(void *);

int

main(int argc, char **argv)

{

pthread_t tidA, tidB;

Pthread_create(&tidA, NULL, &doit, NULL);

Pthread_create(&tidB, NULL, &doit, NULL);

/* wait for both threads to terminate */

Pthread_join(tidA, NULL);

Pthread_join(tidB, NULL);

exit(0);

}

void *

doit(void *vptr)

{

int i, val;

/*

* Each thread fetches, prints, and increments the counter NLOOP times.

* The value of the counter should increase monotonically.

*/

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

Pthread_mutex_lock(&counter_mutex);

val = counter;

printf("%d: %d\n", pthread_self(), val + 1);

counter = val + 1; Pthread_mutex_unlock(&counter_mutex);

}

return (NULL);

}

Page 23: Threads Programming Thread creation Synchronization.

Lock / Unlock Mutexes

pthread_mutex_lock() is used to acquire a lock on the mutex variable. If the mutex variable is already locked, then the thread will block.

pthread_mutex_trylock() is used to acquire a lock on the mutex variable. If the mutex variable is locked, then the thread will NOT block.

pthread_mutex_unlock() will unlock the mutex variable if called by the owning thread.

#include <pthread.h>int pthread_mutex_lock (pthread_mutex_t *mutex);int pthread_mutex_trylock (pthread_mutex_t *mutex);Int pthread_mutex_unlock (pthread_mutex_t *mutex);

Page 24: Threads Programming Thread creation Synchronization.

Condition Variables pthreads support condition variables, which allow one

thread to wait (sleep) for an event generated by any other thread. This allows us to avoid the busy waiting problem. Mutex variables prevent simultaneous access to a shared

variable Condition variables allow threads to synchronize on the

actual value of a shared variable Without condition variables, the programmer

would need to have threads continually polling to check if the condition is met.

A condition variable is always used in conjunction with a mutex lock

Page 25: Threads Programming Thread creation Synchronization.

Busy Waiting Problem

active_threads=0;

// start up n threads on first n clients

// make sure they are all running

while (1) {

// have to lock/release active_threads

if (active_threads < n) //busy waiting

// start up thread for next client

}

Page 26: Threads Programming Thread creation Synchronization.

Condition Variables (cont.)

A condition variable is always used with mutex. pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr); pthread_cond_signal(pthread_cond_t *cptr);

don’t let the word signal confuse you -this has nothing to do with Unix signals

Page 27: Threads Programming Thread creation Synchronization.

• Each thread decrements active_threads when terminating and calls pthread_cond_signal to wake up the main loop.

• The main thread increments active_threads when each thread is started and waits for changes by calling pthread_cond_wait.

Condition Variables

Page 28: Threads Programming Thread creation Synchronization.

• All changes to active_threads must be inside the lock and release of a mutex.

• If two threads are ready to exit at (nearly) the same time – the second must wait until the main loop recognizes the first.

• We don’t lose any of the condition signals.

Condition Variables

Page 29: Threads Programming Thread creation Synchronization.

Global Variables// global variable the number of active

// threads (clients)

int active_threads=0;

// mutex used to lock active_threads

pthread_mutex_t at_mutex = PTHREAD_MUTEX_INITIALIZER;

// condition var. used to signal changes

pthread_cond_t at_cond = PTHREAD_COND_INITIALIZER;

Page 30: Threads Programming Thread creation Synchronization.

Child Thread Code

void *cld_func(void *arg) {

. . .

// handle the client

. . .

pthread_mutex_lock(&at_mutex);

active_threads--;

pthread_cond_signal(&at_cond);

pthread_mutex_unlock(&at_mutex);

return();

}

Page 31: Threads Programming Thread creation Synchronization.

Main thread// no need to lock yet

active_threads=0;

while (1) {

pthread_mutex_lock(&at_mutex);

while (active_threads < n ) {

active_threads++;

pthread_create(…)

}

pthread_cond_wait( &at_cond, &at_mutex);

pthread_mutex_unlock(&at_mutex);

}

Page 32: Threads Programming Thread creation Synchronization.

Thread A1. Work up to the point where a certain

condition must occur 2. Lock associated mutex mx and check

value of a shared variable cv3. Call pthread_cond_wait(cv,mx) to

perform a blocking wait for signal from Thread-B. (Note: the blocking wait automatically and atomically unlocks the mutex mx )

4. When signalled, wake up. Mutex mx is automatically and atomically locked.

5. Explicitly unlock mutex mx 6. Continue

Thread B1. Do work

2. Lock associated mutex mx Change the value of the shared variable cv that Thread-A is waiting upon.

3. Check value of the global Thread-A wait variable cv. If it fulfills the desired condition, signal Thread-A.

4. Unlock mutex mx.

5. Continue

1. Declare and initialize global data/variables which require synchronization (such as "count")

2. Declare and initialize a condition variable object

3. Declare and initialize an associated mutex

4. Create threads A and B to do work

Typical Condition Variable Sequence

MAIN THREAD

Page 33: Threads Programming Thread creation Synchronization.

Waiting / Signaling on Condition Variables

pthread_cond_wait() blocks the calling thread until the specified condition is satisfied. It should be called with mutex locked.

pthread_cond_signal() is used to wake up another thread which is waiting on the condition. It should unlock mutex after calling pthread_cond_signal().

pthread_cond_broadcast() is used if there are more than one waiting threads.

#include <pthread.h>int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);int pthread_cond_signal (pthread_cond_t *cond);Int pthread_cond_broadcast (pthread_cond_t *cond);