Threads and Multithreading
Threads and Multithreading
- Multithreading allows a process to have other threads of execution. - Fork is used to create a new (child) process; problems with fork: 1. fork is too expensive mainly because memory has to be copied from parent to the child process (e.g. duplicating all descriptors) 2. Since each process has its own address space, IPC is required to pass information between parent and child after the fork. Reference: https://computing.llnl.gov/tutorials/pthreads/
1
errno
where
- tid is the thread ID (returned by the call) - attr is a set of attributes. Each thread has several attributes: its initial stack size, its priority, whether it should be a daemon thread or not, and so on. We can specify these attributes by initializing a pthread_attr_t variable that overrides the defaults. Normally the default values are taken, by specifying the attr argument as null pointer. - func is the address of thread start function. The thread starts by calling this function and then terminates either explicitly (by calling pthread_exit) or implicitly (by letting the function return). - arg points to a set of parameters to be passed to the function when it is called. The return value from pthread functions is either 0 if OK or a positive number (Exxx) on an error. (for example, if pthread_create() cannot create a new thread because system limitations (on number of threads) exceeded, then return value is EAGAIN)
6
One thread can wait for another thread to terminate by calling the pthread_join function: (similar to waitpid() for process)
#include <pthread.h>
int
#include <pthread.h>
Detach
The term detaching means cleaning up after a thread and reclaiming its storage space. Two ways to achieve this:
- by calling the pthread_join function and waiting for it to terminate, or - by setting the detach attribute of the thread (either at creation time or dynamically by calling the pthread_detach function:
(a thread is either joinable or detached)
9
#include <pthread.h>
int
If the detached attribute is set , the thread is not joinable and its storage space may be reclaimed automatically when the thread terminates. When a program terminates all its threads terminate. Hence, sometimes it is necessary for a main program to issue a pthread_join.
10
11
If the thread is not detached, its thread ID and exit status are retained for a later pthread_join by some other thread .
12
multiple threads is not straightforward, because some threads in the process may hold resources or executing system calls.
- The POSIX standard specifies that the child process will only have one thread.
13
Mutexes and condition variables, when combined, provide the functionality of a monitor but with a procedure interface.
Each monitor has an associated mutex variable and all operations on the monitor are surrounded by calls to lock and unlock the mutex.
15
Dynamically allocated mutexes can be initialized at run time by calling the pthread_mutex_init function. Lock and unlock functions:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr);
* If the mutex is locked by some other thread, then we are blocked until the mutex is unlocked.
18
#include "pthread.h" #define NLOOP 5000 int counter; 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); 19 }
#include "pthread.h" #define NLOOP 5000 int counter; /* this is incremented by the 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); 20 }
Example: The Multiple-Producers/Single-Consumer Problem: - Producers and consumer are represented by threads, - buff is an integer array to hold the shared data, - producers set buff[i] to i, - consumer verifies that each entry is correct, - consumer starts after all producers terminate.
21
Example: mutex-producer-consumer
#include #define #define "unpipc.h" MAXNITEMS MAXNTHREADS 1000 100
/* globals shared by threads */ int nitems; /* read-only by producer and consumer */ struct { pthread_mutex_t mutex; int buff[MAXNITEMS]; int nput; /* next index to store */ int nval; /* next value to store */ } shared = { PTHREAD_MUTEX_INITIALIZER }; /* end globals */
22
void
/* include main */ int main(int argc, char **argv) { int i, nthreads, count[MAXNTHREADS]; pthread_t tid_produce[MAXNTHREADS], tid_consume; if (argc != 3) err_quit("usage: prodcons <#items> <#threads>"); nitems = min(atoi(argv[1]), MAXNITEMS); nthreads = min(atoi(argv[2]), MAXNTHREADS);
23
Set_concurrency(nthreads + 1); /* in Solaris 2.6: Thr_SetConcurrency(nthreads+1) /* create all producers and one consumer */ for (i = 0; i < nthreads; i++) { count[i] = 0; pthread_create(&tid_produce[i], NULL, produce, &count[i]); }
/* wait for all producers */ for (i = 0; i < nthreads; i++) { pthread_join(tid_produce[i], NULL); printf("count[%d] = %d\n", i, count[i]); } Pthread_create(&tid_consume, NULL, consume,NULL); Pthread_join(tid_consume, NULL); exit(0); } /* end main */
24
void * consume(void *arg) { int i; for (i = 0; i < nitems; i++) { if (shared.buff[i] != i) printf("buff[%d] = %d\n", i, shared.buff[i]); } return(NULL); } /* end prodcons */
26
A condition variable is always associated with a mutex. The condition variable is a signaling mechanism associated with a mutex. When a thread waits on a condition variable, its lock on the associated mutex is released. When the thread successfully returns from the wait, it again holds the lock.
However, because more than one thread may be released, the programmer must again test for the condition which caused it to wait initially.
28
29
The producers/consumer example where the consumer thread starts right after all producer threads have started:
/* globals shared by threads */ int nitems; /* read-only by producer and consumer int buff[MAXNITEMS]; int Nsignals; struct { pthread_mutex_t mutex; int nput; /* next index to store */ int nval; /* next value to store */ } put = { PTHREAD_MUTEX_INITIALIZER }; /** struct put is used by producer only ***/ struct{ pthread_mutex_t mutex; pthread_cond_t cond; int nready /* number ready for consumer } nready = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER,0 };
*/
*/
31
for (i = 0; i < nthreads; i++) { count[i] = 0; Pthread_create(&tid_produce[i], NULL, produce, &count[i]); } pthread_create(&tid_consume, NULL, consume, NULL);
32
/* wait for all producers and the consumer*/ for (i = 0; i < nthreads; i++) { Pthread_join(tid_produce[i], NULL); printf("count[%d] = %d\n", i, count[i]); } Pthread_join(tid_consume, NULL); printf("nsignals = %d\n", Nsignals);
exit(0);
33
34
35
Additional functions #include <pthread.h> int pthread_cond_broadcast(pthread_cond_t *cptr,); It wakes up all threads that are blocked on the condition variable. int pthread_cond_timedwait( pthread_cond_t *cptr, pthread_mutex_t *mutex, const struct timespec *abstime); abstime is a timespec structure: struct timespec{ time_t tv_sec; long tv_nsec; };
/* seconds */ /* nanosecond */
The structure specifies the system time (absolute time) when the function must return, even if the condition variable has not been signaled yet.
36