Basic Thread Management

Once a thread is created, we can direct the calling process to wait until the thread is finished (it calls pthread_exit or is cancelled). This is accomplished with a call to the pthread_join library function shown in Table 11.3.

The first argument is a valid thread ID (as returned from the pthread_create call). The specified thread must be associated with the calling process and should not be specified as detached. The second argument, **status , references a static location where the completion status of the waited upon thread will be stored. The status value is the value passed to pthread_exit , or the value returned when the function code reaches a return statement. [6] If the second argument to pthread_create is set to NULL, the status information will be discarded.

[6] If the thread involuntarily terminates, its status information is not relevant.

Table 11.3. The pthread_join Library Function.

Include File(s)

Manual Section

3

Summary

int pthread_join( pthread_t target_thread, void **status );

Return

Success

Failure

Sets errno

Nonzero

 

There are some caveats associated with joining threads. A thread should bewaited upon (joined) by only one other thread. The thread issuing the joindoes not need to be the initiating thread. If multiple threads wait for the same thread to complete, only one will receive the correct status information. The joins in competing threads will return an error. Should the thread initiating the join be canceled , the waited upon thread can be joined by another thread. [7] If the targeted thread has terminated prior to the issuing of the call to pthread_join , the call will return immediately and will not block. Last, but certainly not least, a nondetached thread (which is the default) that is not joined will not release its resources when the thread finishes and will only release its resources when its creating process terminates. Such threads can be the source of memory leaks. If pthread_join is successful, it returns a 0; otherwise , it returns a nonzero value. The return of ESRCH (3) means an undetached thread for the corresponding thread ID could not be found or the thread ID was set to 0. The return of EINVAL (22) means the thread specified by the thread ID is detached or the calling thread has already issued a join for the same thread ID. If EDEADLK (35) is returned, it indicates a deadlock situation has been encountered .

[7] With POSIX threads the user can issue a cancellation of a specific thread. The thread may have had several cleanup routines associated with it. If one of the associated cleanup routines contains a call to pthread_detach , a subsequent call to pthread_join will fail.

The process of joining a thread is somewhat analogous to a process waiting on a forked child process. However, unlike a forked child process, a thread can become detached with a single library call. When a detached thread finishes, its resources are automatically returned to the system. The pthread_detach library call (Table 11.4) is used to dynamically detach a joinable thread. In a later section the generation of a detached thread using a thread attribute object will be addressed.

Table 11.4. The pthread_detach Library Function.

Include File(s)

Manual Section

3

Summary

int pthread_detach (pthread_t threadID);

Return

Success

Failure

Sets errno

Nonzero

 

The pthread_detach library function accepts a thread ID as its only argument. If successful, the call to pthread_detach detaches the indicated thread and returns a 0 value. If the call fails, the indicated thread is not detached, and a nonzero value is returned. The value EINVAL (22) is returned if an attempt to detach an already detached thread is made, or ESRCH (3) is returned if no such thread ID is found. Remember, once a thread is detached, other threads can no longer synchronize their activities based on its termination.

Program 11.1 uses the pthread_create and pthread_join library calls to create and join several threads.

Program 11.1 Creating and joining threads.

File : p11.1.cxx /* Creating and joining threads */ #define _GNU_SOURCE + #define _REENTRANT #include #include #include #include 10 #include #include #include using namespace std; int MAX=5; + inline int my_rand( int, int ); void *say_it( void * ); int main(int argc, char *argv[]) { pthread_t thread_id[MAX]; 20 int status, *p_status = &status; setvbuf(stdout, (char *) NULL, _IONBF, 0); if ( argc > MAX+1 ){ // check arg list cerr << *argv << " arg1, arg2, ... arg" << MAX << endl; return 1; + } cout << "Displaying" << endl; for (int i = 0; i < argc-1; ++i) { // generate threads if( pthread_create(&thread_id[i],NULL,say_it,(void *)argv[i+1]) > 0){ cerr << "pthread_create failure" << endl; 30 return 2; } } for (int i=0; i < argc-1; ++i){ // wait for each thread if ( pthread_join(thread_id[i], (void **) p_status) > 0){ + cerr << "pthread_join failure" << endl; return 3; } cout << endl << "Thread " << thread_id[i] << " returns " << status; 40 } cout << endl << "Done" << endl; return 0; } // Display the word passed a random # of times + void * say_it(void *word) { int numb = my_rand(2,6); cout << (char *)word << " to be printed " << numb << " times." << endl; 50 for (int i=0; i < numb; ++i){ sleep(1); cout << (char *) word << " "; } return (void *) NULL; + } // Generate a random # within given range int my_rand(int start, int range){ struct timeval t; 60 gettimeofday(&t, (struct timezone *)NULL); return (int)(start+((float)range * rand_r((unsigned *)&t.tv_usec)) / (RAND_MAX+1.0)); }

When Program 11.1 executes, it examines the number of arguments passed in on the command line. For each argument (up to a limit of 5), the program creates a separate thread. As each thread is created, its thread ID is saved in the thread_id array. As written, the program passes pthread_create a NULL value as its second argument; therefore, each thread created has the default set of attributes. The user-defined function say_it is passed as the starting point for each thread. The appropriate command-line argument is passed to the say_it function as the fourth argument of pthread_create . [8] Following the creation of the threads, the program waits for the threads to finish using the pthread_join library function call. The value returned from each thread and its thread ID is displayed.

[8] Note the type casting. If necessary, we can also use type casting when passing the function reference, using the less than intuitive typecast (void * (*)()) .

The user-defined say_it function is used to display the passed-in sequence of characters a random number of times. At the start of the say_it function, a random value is calculated. The library functions srand and rand that we have used previously are not used, as they are not safe to use in a multiple thread setting. However, there is a library function, rand_r , that is multithread-safe. The rand_r library function is incorporated into a user-defined inline function called my_rand . In the my_rand function the number of elapsed microseconds since 00:00 Universal Coordinated Time, January 1, returned by the gettimeofday [9] library function, is used as a seed value for rand_r . The value returned by rand_r is then adjusted to fall within the specified limits. The calculated random value and the sequence of characters to be displayed are shown on the screen. Finally, a loop is entered, and for the calculated number of times, the function sleeps one second and then prints the passed-in sequence of characters. A compilation sequence and run of the program is shown in Figure 11.2.

[9] For gettimeofday the file must be included.

Figure 11.2 A Compilation and run of Program 11.1.

linux$ g++ p11.1.c -o p11.1 -lpthread linux$ p11.1 s p a c e Displaying s to be printed 5 times. p to be printed 5 times. a to be printed 5 times. c to be printed 3 times. e to be printed 3 times. s p a c e s p a c e s e p a c s p a a s p Thread 1026 returns 0 Thread 2051 returns 0 Thread 3076 returns 0 <-- 1 Thread 4101 returns 0 Thread 5126 returns 0 Done

(1) Each of these threads is supported by an LWP. Each LWP has its own process ID as well as its thread ID.

In this run, Program 11.1 was passed five command-line arguments: s, p, a, c, e. The program creates five new threads, one for each argument. The number of times each argument will be printed is then displayed. The request to print this information was one of the first lines of code in the user-defined function say_it (see line 48). As shown, all five threads process this statement prior to any one of the threads displaying its individual words. This is somewhat misleading. If we move the sleep statement in the for loop of the say_it function to be after the print statement within the loop, we should see the initial output statements being interspersed with the display ofeach word. If we count the number of words displayed, we will find they correspond to the number promised (e.g., letter s is displayed five times, etc). A casual look at the remainder of the output might lead one to believe the threads exit in an orderly manner. The pthread_join 's are done in a second loop in the calling function ( main ). Since the thread IDs are passed to pthread_join in order, the information concerning their demise is also displayed in order. Viewing the output, we have no way to tell which thread ended first (even though it would seem reasonable that one of the threads that had to display the fewest number of words would be first). When each thread finishes with the say_it function, it returns a NULL. This value, which is picked up by the pthread_join , is displayed as a 0. The return statement in the say_it function can be replaced with a call to pthread_exit . However, if we replace the return with pthread_exit , most compilers will complain that no value is returned by the say_it function, forcing us to include the return statement even if it is unreachable! If we run this program several times, the output sequences will vary.

As written, the display of each word (command-line argument) is preceded by a call to sleep for one second. In the run shown in Figure 11.3, sleep is called 19 times (7 for f , 5 for a , etc.). Yet, the length of time it takes for the program to complete is far less than 19 seconds. This is to be expected, as each thread is sleeping concurrently. We can verify the amount of time used by the program using the /usr/bin/time [10] utility. Several reruns of Program 11.1 using the /usr/bin/time utility confirms our conjecture.

[10] In most versions of UNIX there are several utilities that provide statistics about the amount of time it takes to execute a particular command (or program). The most common of these utilities are time , /usr/bin/time , and timex . Most versions of Linux do not come with timex .

Figure 11.3 Timing a run of Program 11.1.

linux$ /usr/bin/time -p p11.1 f a c e Displaying f to be printed 7 times. a to be printed 5 times. c to be printed 3 times. e to be printed 4 times. f a c e f a c e f e c a f e a af f f Thread 1026 returns 0 Thread 2051 returns 0 Thread 3076 returns 0 Thread 4101 returns 0 Done real 7.07 <-- 1 user 0.00 <-- 2 sys 0.02 <-- 3

(1) Elapsed real time (in seconds).

(2) CPU seconds in user mode

(3) CPU seconds in kernel mode

EXERCISE

The output of Program 11.1 shows the system assigns the thread ID 1026 to the first thread it creates, 2051 to the next , and so on (incrementing each time by 1025). To explore the generation of threads by the program, compile p11.1.cxx with the -g option and load it into the gdb debugger ( linux$ gdb p11.1 ). Set a breakpoint at line 55 of the program at the return statement in the say_it function (to accomplish this, at the gdb prompt enter the command break 55 ). Now run the program with two arguments (i.e., at the gdb prompt enter run A B ). When the program stops execution, at the gdb prompt enter the command info thread . How many threads are generated? Run the program with three arguments. Now how many threads are generated? How do you account for the extra thread(s)?

If your system supports the user command strace , try the command-line sequence

linux$ strace -c -f p11.1 A B C > /dev/null

Is the number of calls to the clone system call the same as the number of threads generated? Why? Note that when threaded programs fail (or are interrupted ), they may generate a core file for each thread. If space is at a premium, you may want to remove these files.

Категории