330 likes | 522 Views
Threads Programming. Thread creation Synchronization. Threads vs. Processes. Creation of a new process using fork is expensive (time & memory). IPC is required to pass information between parent and child
E N D
Threads Programming Thread creation Synchronization
Threads vs. Processes Creation 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
Process A Process B Global Variables Global Variables Code Code Stack Stack fork() fork()
Global Variables Code Stack pthread_create() Process A Thread 1 pthread_create() Process A Thread 2 Stack
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
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)
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
Client.c void *copyto (void *); static int sockfd; /* global for both threads to access */ static FILE *fp; void str_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 */ }
TCP Echo Server using Threads 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); } } 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); }
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)
pthread_create() The return value is 0 for OK. Positive error number on error. Does not set errno !!! Thread ID is returned in tid
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 *.
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!
Thread Lifecycle – joinable thread similar to waitpid(), calling thread blocks thread attributes specified status returned • Thread Termination • returns from its starting routing (main program) • call pthread_exit • cancelled by another thread pthread_cancel • process terminated
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);
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)
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
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); }
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
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); }
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);
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
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 }
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
Condition Variables • 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 • 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.
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;
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(); }
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); }
Thread A Work up to the point where a certain condition must occur Lock associated mutex mx and check value of a shared variable cv 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) When signalled, wake up. Mutex mx is automatically and atomically locked. Explicitly unlock mutex mx Continue ThreadB Do work Lock associated mutex mx Change the value of the shared variable cv that Thread-A is waiting upon. Check value of the global Thread-A wait variable cv. If it fulfills the desired condition, signal Thread-A. Unlock mutex mx. Continue Typical Condition Variable Sequence Declare and initialize global data/variables which require synchronization (such as "count") Declare and initialize a condition variable object Declare and initialize an associated mutex Create threads A and B to do work MAIN THREAD
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);