Threads: Name of Manual
Threads: Name of Manual
Name of Manual
[Link]
Copyright 2006 Aricent Inc. All Rights Reserved. No part of this document may be reproduced, stored in a retrieval system or transmitted, in any form, or by any means, electronic or otherwise, including photocopying, reprinting, or recording, for any purpose, without the express written permission of Aricent. Printed in ________ TRADEMARKS ARICENT and THE ARICENT LOGO are trademarks of Aricent Inc. in the U.S. and other countries. The use of any of these trademarks without Aricent prior written consent is strictly prohibited. Other trademarks and trade names may be used in this document to refer to either the entities claiming the marks and names or their products. Aricent Inc. disclaims any proprietary interest in the trademarks and trade names other than its own. DISCLAIMER The information in this book is provided as is, with no warranties whatsoever, including any warranty of merchantability, fitness for any particular purpose or any warranty otherwise arising out of any proposal, specification or sample. This document is provided for informational purposes only and should not be construed as a commitment on the part of Aricent. Information in this document is subject to change without notice. REQUESTS For information or obtaining permission for use of material of this work, please submit a written request to: Corporate Marketing and Legal, 3460 Hillview Avenue, Palo Alto, Ca. 94304. DOCUMENT No.: Document Number
Contents
CHAPTER 1: CHAPTER 2:
ABSTRACT_____________________________________________________4 PROCESSES AND THREADS______________________________________5 WAIT FOR THREAD TERMINATION .........................................................................9 USE THE PTHREAD_JOIN FUNCTION TO WAIT FOR A THREAD TO TERMINATE. IT IS PROTOTYPED BY: .................................................................................................. 9 DETACHING A THREAD ......................................................................................... 9 EXAMPLE: PTHREAD CREATION AND TERMINATION......................................10 JOINING AND DETACHING THREADS.....................................................................12 CREATING AND DESTROYING MUTEXES...............................................................14 EXAMPLE: USING MUTEXES.......................................................................15 CREATING AND DESTROYING CONDITION VARIABLES...................................19 CONDITION VARIABLES.......................................................................................20 WAITING AND SIGNALING ON CONDITION VARIABLES....................................20 EXAMPLE: USING CONDITION VARIABLES.....................................................20
Abstract
Threads are the basic unit of program execution. A process can have several threads running concurrently, each performing a different job, such as waiting for events or performing a timeconsuming job that the program doesn't need to complete before going on. When a thread has finished its job, the thread is destroyed. What do a t-shirt and a computer program have in common? They are both composed of many threads! While the threads in a t-shirt hold the shirt together, the threads of a computer program allow the program to execute sequential actions or many actions at once. Each thread in a program identifies a process that runs when the program asks it to (unlike when you ask your roommate to do the dishes). Threads are typically given a certain priority, meaning some threads take precedence over others. Once the CPU is finished processing one thread, it can run the next thread waiting in line. Threads seldom have to wait more than a few milliseconds before they run. Computer programs that implement "multi-threading" can execute multiple threads at once. Most modern operating systems support multi-threading at the system level, meaning when one program tries to take up all your CPU resources, you can still switch to other programs and force the CPU-hogging program to share the processor a little bit. The term "thread" can also refer to a series of related postings in an online discussion. Web-based bulletin boards are made up of many topics, or threads. The replies posted in response to the original posting are all part of the same thread.
A process is created by the operating system, and requires a fair amount of "overhead". Processes contain information about program resources and program execution state, including: o o o o o o o o o o o Process ID, process group ID, user ID, and group ID Environment Working directory. Program instructions Registers Stack Heap File descriptors Signal actions Shared libraries Inter-process communication tools (such as message queues, pipes, semaphores, or shared memory).
This independent flow of control is accomplished because a thread maintains its own: o o o o o Stack pointer Registers Scheduling properties (such as policy or priority) Set of pending and blocked signals Thread specific data.
Pthreads APIs
The subroutines which comprise the Pthreads API can be informally grouped into three major classes: 1. Thread management: The first class of functions work directly on threads - creating, detaching, joining, etc. They include functions to set/query thread attributes (joinable, scheduling etc.) 2. Mutexes: The second class of functions deals with synchronization, called a "mutex", which is an abbreviation for "mutual exclusion". Mutex functions provide for creating, destroying, locking and unlocking mutexes. They are also supplemented by mutex attribute functions that set or modify attributes associated with mutexes. 3. Condition variables: The third class of functions address communications between threads that share a mutex. They are based upon programmer specified conditions. This class includes functions to create, destroy, wait and signal based upon specified variable values. Functions to set/query condition variable attributes are also included. Naming conventions: All identifiers in the threads library begin with pthread_ Routine Prefix pthread_ pthread_attr_ pthread_mutex_ pthread_mutexattr_ pthread_cond_ pthread_condattr_ pthread_key_ Functional Group Threads themselves and miscellaneous subroutines Thread attributes objects Mutexes Mutex attributes objects. Condition variables Condition attributes objects Thread-specific data keys
The pthread.h header file must be included in each source file using the Pthreads library. For some implementations, such as IBM's AIX, it may need to be the first include file. The current POSIX standard is defined only for the C language.
Creating Threads:
Use the function pthread_create() to add a new thread of control to the current process. It is prototyped by: int pthread_create(pthread\_t *tid, const pthread\_attr\_t *tattr, void*(*start_routine)(void *), void *arg); When an attribute object is not specified, it is NULL, and the default thread is created with the following attributes: It is unbounded It is nondetached It has a default stack and stack size It inhetits the parent's priority
You can also create a default attribute object with pthread_attr_init() function, and then use this attribute object to create a default thread.. An example call of default thread creation is: #include <pthread.h> pthread_attr_t tattr; pthread_t tid; extern void *start_routine(void *arg); void *arg; int ret; /* default behavior*/ ret = pthread_create(&tid, NULL, start_routine, arg); /* initialized with default attributes */ ret = pthread_attr_init(&tattr); /* default behavior specified*/ ret = pthread_create(&tid, &tattr, start_routine, arg); The pthread_create() function is called with attr having the necessary state behavior. start_routine is the function with which the new thread begins execution. When start_routine returns, the thread exits with the exit status set to the value returned by start_routine. When pthread_create is successful, the ID of the thread created is stored in the location referred to as tid. Creating a thread using a NULL attribute argument has the same effect as using a default attribute; both create a default thread. When tattr is initialized, it acquires the default behavior. pthread_create() returns a zero and exits when it completes successfully. Any other returned value indicates that an error occurred..
Thread Attributes:
By default, a thread is created with certain attributes. The programmer via the thread attribute object can change some of these attributes. pthread_attr_init and pthread_attr_destroy are used to initialize/destroy the thread attribute object. Other routines are then used to query/set specific attributes in the thread attribute object
Wait for Thread Termination Use the pthread_join function to wait for a thread to terminate. It is prototyped by:
int pthread_join (thread_t tid, void **status); An example use of this function is: #include <pthread.h> pthread_t tid; int ret; int status; /* Waiting to join thread "tid" with status */ ret = pthread_join(tid, &status); /* Waiting to join thread "tid" without status */ ret = pthread_join(tid, NULL); The pthread_join() function blocks the calling thread until the specified thread terminates. The specified thread must be in the current process and must not be detached. When status is not NULL, it points to a location that is set to the exit status of the terminated thread when pthread_join() returns successfully. Multiple threads cannot wait for the same thread to terminate. If they try to, one thread returns successfully and the others fail with an error of ESRCH. After pthread_join() returns, any stack storage associated with the thread can be reclaimed by the application. The pthread_join() routine takes two arguments, giving you some flexibility in its use. When you want the caller to wait until a specific thread terminates, supply that thread's ID as the first argument. If you are interested in the exit code of the defunct thread, supply the address of an area to receive it. Remember that pthread_join() works only for target threads that are nondetached. When there is no reason to synchronize with the termination of a particular thread, then that thread should be detached. Think of a detached thread as being the thread you use in most instances and reserve nondetached threads for only those situations that require them.
Detaching a Thread
The function pthread_detach() is an alternative to pthread_join() to reclaim storage for a thread that is created with a detachstate attribute set to PTHREAD_CREATE_JOINABLE. It is prototyped by: int pthread\_detach(thread\_t tid); A simple example of calling this fucntion to detatch a thread is given by: #include <pthread.h> pthread_t tid; int ret; /* detach thread tid */
ret = pthread_detach(tid); The pthread_detach() function is used to indicate to the implementation that storage for the thread tid can be reclaimed when the thread terminates. If tid has not terminated, pthread_detach() does not cause it to terminate. The effect of multiple pthread_detach() calls on the same target thread is unspecified. pthread_detach() returns a zero when it completes successfully. Any other returned value indicates that an error occurred. When any of the following conditions are detected, pthread_detach() fails and returns the an error value.
Terminating Threads:
There are several ways in which a Pthread may be terminated: o o o o The thread returns from its starting routine (the main routine for the initial thread). The thread makes a call to the pthread_exit subroutine (covered below). The thread is canceled by another thread via the pthread_cancel routine (not covered here). The entire process is terminated due to a call to either the exec or exit subroutines.
pthread_exit is used to explicitly exit a thread. Typically, the pthread_exit() routine is called after a thread has completed its work and is no longer required to exist. If main() finishes before the threads it has created, and exits with pthread_exit(), the other threads will continue to execute. Otherwise, they will be automatically terminated when main() finishes. The programmer may optionally specify a termination status, which is stored as a void pointer for any thread that may join the calling thread. Cleanup: the pthread_exit () routine does not close files; any files opened inside the thread will remain open after the thread is terminated.
printf("Creating thread %d\n", t); rc = pthread_create (&threads [t], NULL, PrintHello, (void *) taskids[t]); ... } Example 2 - Thread Argument Passing This example shows how to setup/pass multiple arguments via a structure. Each thread receives a unique instance of the structure. struct thread_data { int thread_id; int sum; char *message; }; struct thread_data thread_data_array[NUM_THREADS]; void *PrintHello(void *threadarg) { struct thread_data *my_data; ... my_data = (struct thread_data *) threadarg; taskid = my_data->thread_id; sum = my_data->sum; hello_msg = my_data->message; ... } int main (int argc, char *argv[]) { ... thread_data_array [t].thread_id = t; thread_data_array [t].sum = sum; thread_data_array [t]. message = messages [t]; rc = pthread_create(&threads[t], NULL, PrintHello, (void *) &thread_data_array [t]); ... } Example 3 - Thread Argument Passing (Incorrect) This example performs argument passing incorrectly. The loop, which creates threads, modifies the contents of the address passed as an argument, possibly before the created threads can access it. int rc, t; for(t=0; t<NUM_THREADS; t++) { printf ("Creating thread %d\n", t); rc = pthread_create (&threads [t], NULL, PrintHello, (void *) &t); ... }
Joining: "Joining" is one way to accomplish synchronization between threads. For example: Joining" is one way to accomplish synchronization between threads. For example:
The pthread_join () subroutine blocks the calling thread until the specified threadid thread terminates. The programmer is able to obtain the target thread's termination return status if it was specified in the target thread's call to pthread_exit (). Two other synchronization methods, mutexes and condition variables, will be discussed later.
Joinable or Not? When a thread is created, one of its attributes defines whether it is joinable or detached. Only threads that are created as joinable can be joined. If a thread is created as detached, it can never be joined. The final draft of the POSIX standard specifies that threads should be created as joinable. However, not all implementations may follow this. To explicitly create a thread as joinable or detached, the attr argument in the pthread_create () routine is used. The typical 4 step process is:
1. 2. 3.
Declare a pthread attribute variable of the pthread_attr_t data type Initialize the attribute variable with pthread_attr_init () Set the attribute detached status with pthread_attr_setdetachstate()
4. When done, free library resources used by the attribute with pthread_attr_destroy() Detaching: The pthread_detach () routine can be used to explicitly detach a thread even though it was created as joinable.
Example Code - Pthread Joining This example demonstrates how to "wait" for thread completions by using the Pthread join routine. Since some implementations of Pthreads may not create threads in a joinable state, the threads in this example are explicitly created in a joinable state so that they can be joined later.
void *BusyWork(void *null) { int i; double result=0.0; for (i=0; i<1000000; i++) { result = result + (double)random(); } printf("result = %e\n",result); pthread_exit((void *) 0); } int main (int argc, char *argv[]) { pthread_t thread[NUM_THREADS]; pthread_attr_t attr; int rc, t, status; /* Initialize and set thread detached attribute */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); for(t=0; t<NUM_THREADS; t++) { printf("Creating thread %d\n", t); rc = pthread_create(&thread[t], &attr, BusyWork, NULL); if (rc) { printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1);
} threads */
/* Free attribute and wait for the other pthread_attr_destroy(&attr); for(t=0; t<NUM_THREADS; t++) { rc = pthread_join(thread[t], (void if (rc) { printf("ERROR; return code from is %d\n", rc); exit(-1); } printf("Completed join with thread } pthread_exit(NULL); }
**)&status); pthread_join()
Mutex Variables
Creating and Destroying Mutexes
Routines: pthread_mutex_init (mutex,attr) pthread_mutex_destroy (mutex) pthread_mutexattr_init (attr) pthread_mutexattr_destroy (attr)
Usage: Mutex variables must be declared with type pthread_mutex_t, and must be initialized before they can be used. There are two ways to initialize a mutex variable:
o o
Prioceiling: Specifies the priority ceiling of a mutex. Process-shared: Specifies the process sharing of a mutex.
Note that not all implementations may provide the three optional mutex attributes. The pthread_mutexattr_init () and pthread_mutexattr_destroy () routines are used to create and destroy mutex attribute objects respectively. pthread_mutex_destroy () should be used to free a mutex object, which is no longer needed.
Mutex Variables
Locking and Unlocking Mutexes
Routines: pthread_mutex_lock (mutex) pthread_mutex_trylock (mutex) pthread_mutex_unlock (mutex)
Usage: The pthread_mutex_lock() routine is used by a thread to acquire a lock on the specified mutex variable. If the mutex is already locked by another thread, this call will block the calling thread until the mutex is unlocked. pthread_mutex_trylock() will attempt to lock a mutex. However, if the mutex is already locked, the routine will return immediately with a "busy" error code. This routine may be useful in preventing deadlock conditions, as in a priority-inversion situation. pthread_mutex_unlock() will unlock a mutex if called by the owning thread. Calling this routine is required after a thread has completed its use of protected data if other threads are to acquire the mutex for their work with the protected data. An error will be returned if: o o If the mutex was already unlocked If the mutex is owned by another thread
#include <malloc.h> /* The following structure contains the necessary information to allow the function "dotprod" to access its input data and place its output into the structure. */ typedef struct { double *a; double *b; double sum; int veclen; } DOTDATA; /* Define globally accessible variables and a mutex */ #define NUMTHRDS 4 #define VECLEN 100 DOTDATA dotstr; pthread_t callThd[NUMTHRDS]; pthread_mutex_t mutexsum; /* The function dotprod is activated when the thread is created. All input to this routine is obtained from a structure of type DOTDATA and all output from this function is written into this structure. The benefit of this approach is apparent for the multi-threaded program: when a thread is created we pass a single argument to the activated function - typically this argument is a thread number. All the other information required by the function is accessed from the globally accessible structure. */ void *dotprod(void *arg) { /* Define and use local variables for convenience */ int i, start, end, offset, len ; double mysum, *x, *y; offset = (int)arg; len = [Link]; start = offset*len; end = start + len; x = dotstr.a; y = dotstr.b; /* Perform the dot product and assign result to the appropriate variable in the structure. */ mysum = 0; for (i=start; i<end ; i++) {
/* Lock a mutex prior to updating the value in the shared structure, and unlock it upon updating. */ pthread_mutex_lock (&mutexsum); [Link] += mysum; pthread_mutex_unlock (&mutexsum); pthread_exit((void*) 0); } /* The main program creates threads which do all the work and then print out result upon completion. Before creating the threads, the input data is created. Since all threads update a shared structure, we need a mutex for mutual exclusion. The main thread needs to wait for all threads to complete, it waits for each one of the threads. We specify a thread attribute value that allow the main thread to join with the threads it creates. Note also that we free up handles when they are no longer needed. int main (int argc, char *argv[]) { int i; double *a, *b; int status; pthread_attr_t attr; /* Assign storage and initialize values */ a = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double)); b = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double)); for (i=0; i<VECLEN*NUMTHRDS; i++) { a[i]=1.0; b[i]=a[i]; } [Link] = VECLEN; dotstr.a = a; dotstr.b = b; [Link]=0; pthread_mutex_init(&mutexsum, NULL); /* Create threads to perform the dotproduct */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); for(i=0; i<NUMTHRDS; i++) { /* Each thread works on a different set of data. The offset is specified by 'i'. The size of
the data for each thread is indicated by VECLEN. */ pthread_create( &callThd[i], &attr, dotprod, (void *)i); } pthread_attr_destroy(&attr); /* Wait on the other threads */ for(i=0; i<NUMTHRDS; i++) { pthread_join( callThd[i], (void **)&status); } /* After joining, print out the results and cleanup */ printf ("Sum = %f \n", [Link]); free (a); free (b); pthread_mutex_destroy(&mutexsum); pthread_exit(NULL);
Condition variable
Overview Condition variables provide yet another way for threads to synchronize. While mutexes implement synchronization by controlling thread access to data, condition variables allow threads to synchronize based upon the actual value of data. Without condition variables, the programmer would need to have threads continually polling (possibly in a critical section), to check if the condition is met. This can be very resource consuming since the thread would be continuously busy in this activity. A condition variable is a way to achieve the same goal without polling. A condition variable is always used in conjunction with a mutex lock. A representative sequence for using condition variables is shown below. Main Thread 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 Thread A Do work up to the point where a certain condition must occur (such as "count" must reach a specified value) Lock associated mutex and check value of a global variable Thread B Do work Lock associated mutex Change the value of the global variable that
Call pthread_cond_wait () to perform a blocking wait for signal from Thread-B. Note that a call to pthread_cond_wait () automatically and atomically unlocks the associated mutex variable so that it can be used by Thread-B. When signalled, wake up. Mutex is automatically and atomically locked. Explicitly unlock mutex Continue Main Thread Join / Continue
Thread-A is waiting upon. Check value of the global Thread-A wait variable. If it fulfills the desired condition, signal Thread-A. Unlock mutex. Continue
Condition Variables
Creating and Destroying Condition Variables
Routines: pthread_cond_init (condition,attr) pthread_cond_destroy (condition) pthread_condattr_init (attr) pthread_condattr_destroy (attr)
Usage: Condition variables must be declared with type pthread_cond_t, and must be initialized before they can be used. There are two ways to initialize a condition variable:
The optional attr object is used to set condition variable attributes. There is only one attribute defined for condition variables: process-shared, which allows the condition variable to be seen by threads in other processes. The attribute object, if used, must be of type pthread_condattr_t (may be specified as NULL to accept defaults). Note that not all implementations may provide the process-shared attribute.
The pthread_condattr_init() and pthread_condattr_destroy() routines are used to create and destroy condition variable attribute objects.
Usage: pthread_cond_wait() blocks the calling thread until the specified condition is signalled. This routine should be called while mutex is locked, and it will automatically release the mutex while it waits. After signal is received and thread is awakened, mutex will be automatically locked for use by the thread. The programmer is then responsible for unlocking mutex when the thread is finished with it. The pthread_cond_signal() routine is used to signal (or wake up) another thread, which is waiting on the condition variable. It should be called after mutex is locked, and must unlock mutex in order for pthread_cond_wait() routine to complete. The pthread_cond_broadcast () routine should be used instead of pthread_cond_signal () if more than one thread is in a blocking wait state. It is a logical error to call pthread_cond_signal () before calling pthread_cond_wait ().
int j,i; double result=0.0; int *my_id = idp; for (i=0; i<TCOUNT; i++) { pthread_mutex_lock(&count_mutex); count++; /* Check the value of count and signal waiting thread when condition is reached. Note that this occurs while mutex is locked. */ if (count == COUNT_LIMIT) { pthread_cond_signal(&count_threshold_cv); printf("inc_count(): thread %d, count = %d Threshold reached.\n", *my_id, count); } printf("inc_count(): thread %d, count = %d, unlocking mutex\n", *my_id, count); pthread_mutex_unlock(&count_mutex); /* Do some work so threads can alternate on mutex lock */ for (j=0; j<1000; j++) result = result + (double)random(); } pthread_exit(NULL);
void *watch_count(void *idp) { int *my_id = idp; printf("Starting watch_count(): thread %d\n", *my_id); /* Lock mutex and wait for signal. Note that the pthread_cond_wait routine will automatically and atomically unlock mutex while it waits. Also, note that if COUNT_LIMIT is reached before this routine is run by the waiting thread, the loop will be skipped to prevent pthread_cond_wait from never returning. */ pthread_mutex_lock(&count_mutex); while (count<COUNT_LIMIT) { pthread_cond_wait(&count_threshold_cv, &count_mutex); printf("watch_count(): thread %d Condition signal received.\n", *my_id); } pthread_mutex_unlock(&count_mutex); pthread_exit(NULL); } int main (int argc, char *argv[]) { int i, rc; pthread_t threads[3]; pthread_attr_t attr;
/* Initialize mutex and condition variable objects */ pthread_mutex_init(&count_mutex, NULL); pthread_cond_init (&count_threshold_cv, NULL); /* For portability, explicitly create so that they can be joined later. */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, pthread_create(&threads[0], &attr, pthread_create(&threads[1], &attr, pthread_create(&threads[2], &attr, threads in a joinable state
PTHREAD_CREATE_JOINABLE); inc_count, (void *)&thread_ids[0]); inc_count, (void *)&thread_ids[1]); watch_count, (void *)&thread_ids[2]);
/* Wait for all threads to complete */ for (i=0; i<NUM_THREADS; i++) { pthread_join(threads[i], NULL); } printf ("Main(): Waited on %d threads. Done.\n", NUM_THREADS); /* Clean up and exit */ pthread_attr_destroy(&attr); pthread_mutex_destroy(&count_mutex); pthread_cond_destroy(&count_threshold_cv); pthread_exit(NULL);