Creating and Terminating Threads
Routines: (thread,attr,start_routine,arg) (status) (attr) (attr) |
Creating Threads:
- Initially, your main() program comprises a single, default thread. All other threads must be explicitly created by the programmer.
- pthread_create creates a new thread and makes it executable. This routine can be called any number of times from anywhere within your code.
- pthread_create arguments:
- thread: An opaque, unique identifier for the new thread returned by the subroutine.
- attr: An opaque attribute object that may be used to set thread attributes. You can specify a thread attributes object, or NULL for the default values.
- start_routine: the C routine that the thread will execute once it is created.
- arg: A single argument that may be passed to start_routine. It must be passed by reference as a pointer cast of type void. NULL may be used if no argument is to be passed.
- The maximum number of threads that may be created by a process is implementation dependent.
- Once created, threads are peers, and may create other threads. There is no implied hierarchy or dependency between threads.
Thread Attributes:
- By default, a thread is created with certain attributes. Some of these attributes can be changed by the programmer via the thread attribute object.
- 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.
- Some of these attributes will be discussed later.
Terminating Threads:
- There are several ways in which a Pthread may be terminated:
- 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.
- Discussion: In subroutines that execute to completion normally, you can often dispense with calling pthread_exit() - unless, of course, you want to pass a return code back. However, in main(), there is a definite problem if main() completes before the threads it spawned. If you don't call pthread_exit()explicitly, when main() completes, the process (and all threads) will be terminated. By calling pthread_exit() in main(), the process and all of its threads will be kept alive even though all of the code in main() has been executed.
Example: Pthread Creation and Termination
- This simple example code creates 5 threads with the pthread_create() routine. Each thread prints a "Hello World!" message, and then terminates with a call to pthread_exit().
Passing Arguments to Threads
- The pthread_create() routine permits the programmer to pass one argument to the thread start routine. For cases where multiple arguments must be passed, this limitation is easily overcome by creating a structure which contains all of the arguments, and then passing a pointer to that structure in thepthread_create() routine.
- All arguments must be passed by reference and cast to (void *).
Example 1 - Thread Argument PassingThis code fragment demonstrates how to pass a simple integer to each thread. The calling thread uses a unique data structure for each thread, insuring that each thread's argument remains intact throughout the program.
long *taskids[NUM_THREADS];
for(t=0; trc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskids[t]);
...
}
|
Example 2 - Thread Argument PassingThis 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. It passes the address of variable t, which is shared memory space and visible to all threads. As the loop iterates, the value of this memory location changes, possibly before the created threads can access it.
int rc;
long t;
for(t=0; trc = pthread_create(&threads[t], NULL, PrintHello, (void *) &t);
...
}
|
Joining and Detaching Threads
Routines: (threadid,status) (threadid) (attr,detachstate) (attr,detachstate) |
Joining:
- "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().
- A joining thread can match one pthread_join() call. It is a logical error to attempt multiple joins on the same thread.
- 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.
- 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:
- 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()
- 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.
- There is no converse routine.
Recommendations:
- If a thread requires joining, consider explicitly creating it as joinable. This provides portability as not all implementations may create threads as joinable by default.
- If you know in advance that a thread will never need to join with another thread, consider creating it in a detached state. Some system resources may be able to be freed.
Example: Pthread Joining
Example Code - Pthread JoiningThis 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.
#include
#include
#include
#include
#define NUM_THREADS 4
void *BusyWork(void *t)
{
int i;
long tid;
double result=0.0;
tid = (long)t;
printf("Thread %ld starting...\n",tid);
for (i=0; i<1000000; i++)
{
result = result + sin(i) * tan(i);
}
printf("Thread %ld done. Result = %e\n",tid, result);
pthread_exit((void*) t);
}
int main (int argc, char *argv[])
{
pthread_t thread[NUM_THREADS];
pthread_attr_t attr;
int rc;
long t;
void *status;
/* Initialize and set thread detached attribute */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for(t=0; trc = pthread_create(&thread[t], &attr, BusyWork, (void *)t);
if (rc) {
printf("ERROR; return code from pthread_create()
is %d\n", rc);
exit(-1);
}
}
/* Free attribute and wait for the other threads */
pthread_attr_destroy(&attr);
for(t=0; trc = pthread_join(thread[t], &status);
if (rc) {
printf("ERROR; return code from pthread_join()
is %d\n", rc);
exit(-1);
}
printf("Main: completed join with thread %ld having a status
of %ld\n",t,(long)status);
}
printf("Main: program completed. Exiting.\n");
pthread_exit(NULL);
}
|
Stack Management
Routines: (attr, stacksize) (attr, stacksize) (attr, stackaddr) (attr, stackaddr) |
Preventing Stack Problems:
- The POSIX standard does not dictate the size of a thread's stack. This is implementation dependent and varies.
- Exceeding the default stack limit is often very easy to do, with the usual results: program termination and/or corrupted data.
- Safe and portable programs do not depend upon the default stack limit, but instead, explicitly allocate enough stack for each thread by using thepthread_attr_setstacksize routine.
- The pthread_attr_getstackaddr and pthread_attr_setstackaddr routines can be used by applications in an environment where the stack for a thread must be placed in some particular region of memory.
Some Practical Examples at LC:
- Default thread stack size varies greatly. The maximum size that can be obtained also varies greatly, and may depend upon the number of threads per node.
Node Architecture | #CPUs | Memory (GB) | Default Size (bytes) |
---|
AMD Opteron | 8 | 16 | 2,097,152 |
Intel IA64 | 4 | 8 | 33,554,432 |
Intel IA32 | 2 | 4 | 2,097,152 |
IBM Power5 | 8 | 32 | 196,608 |
IBM Power4 | 8 | 16 | 196,608 |
IBM Power3 | 16 | 16 | 98,304 |
Example: Stack Management
Example Code - Stack ManagementThis example demonstrates how to query and set a thread's stack size.
#include
#include
#define NTHREADS 4
#define N 1000
#define MEGEXTRA 1000000
pthread_attr_t attr;
void *dowork(void *threadid)
{
double A[N][N];
int i,j;
long tid;
size_t mystacksize;
tid = (long)threadid;
pthread_attr_getstacksize (&attr, &mystacksize);
printf("Thread %ld: stack size = %li bytes \n", tid, mystacksize);
for (i=0; ipthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t threads[NTHREADS];
size_t stacksize;
int rc;
long t;
pthread_attr_init(&attr);
pthread_attr_getstacksize (&attr, &stacksize);
printf("Default stack size = %li\n", stacksize);
stacksize = sizeof(double)*N*N+MEGEXTRA;
printf("Amount of stack needed per thread = %li\n",stacksize);
pthread_attr_setstacksize (&attr, stacksize);
printf("Creating threads with stack size = %li bytes\n",stacksize);
for(t=0; tpthread_create(&threads[t], &attr, dowork, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
printf("Created %ld threads.\n", t);
pthread_exit(NULL);
}
|
Miscellaneous Routines
阅读(1335) | 评论(0) | 转发(0) |