65
4.1 Thread Creation
pthread_t thread2_id;
struct char_print_parms thread1_args;
struct char_print_parms thread2_args;
/* Create a new thread to print 30,000 ’x’s. */
thread1_args.character = ’x’;
thread1_args.count = 30000;
pthread_create (&thread1_id, NULL, &char_print, &thread1_args);
/* Create a new thread to print 20,000 o’s. */
thread2_args.character = ’o’;
thread2_args.count = 20000;
pthread_create (&thread2_id, NULL, &char_print, &thread2_args);
return 0;
}
But wait! The program in Listing 4.2 has a serious bug in it.The main thread (which
runs the main function) creates the thread parameter structures (thread1_args and
thread2_args) as local variables, and then passes pointers to these structures to the
threads it creates.What’s to prevent Linux from scheduling the three threads in such a
way that main finishes executing before either of the other two threads are done?
Nothing! But if this happens, the memory containing the thread parameter structures
will be deallocated while the other two threads are still accessing it.
4.1.2 Joining Threads
One solution is to force main to wait until the other two threads are done.What we
need is a function similar to wait that waits for a thread to finish instead of a process.
That function is pthread_join, which takes two arguments: the thread ID of the
thread to wait for, and a pointer to a void* variable that will receive the finished
thread’s return value. If you don’t care about the thread return value, pass NULL as the
second argument.
Listing 4.3 shows the corrected main function for the buggy example in Listing 4.2.
In this version,
main does not exit until both of the threads printing x’s and o’s have
completed, so they are no longer using the argument structures.
Listing 4.3 Revised Main Function for thread-create2.c
int main ()
{
pthread_t thread1_id;
pthread_t thread2_id;
struct char_print_parms thread1_args;
struct char_print_parms thread2_args;
continues
05 0430 CH04 5/22/01 10:21 AM Page 65
66
Chapter 4 Threads
/* Create a new thread to print 30,000 x’s. */
thread1_args.character = ’x’;
thread1_args.count = 30000;
pthread_create (&thread1_id, NULL, &char_print, &thread1_args);
/* Create a new thread to print 20,000 o’s. */
thread2_args.character = ’o’;
thread2_args.count = 20000;
pthread_create (&thread2_id, NULL, &char_print, &thread2_args);
/* Make sure the first thread has finished. */
pthread_join (thread1_id, NULL);
/* Make sure the second thread has finished. */
pthread_join (thread2_id, NULL);
/* Now we can safely return. */
return 0;
}
The moral of the story: Make sure that any data you pass to a thread by reference is
not deallocated, even by a different thread, until you’re sure that the thread is done with
it.This is true both for local variables, which are deallocated when they go out of
scope, and for heap-allocated variables, which you deallocate by calling free (or using
delete in C++).
4.1.3 Thread Return Values
If the second argument you pass to pthread_join is non-null, the thread’s return value
will be placed in the location pointed to by that argument.The thread return value,
like the thread argument, is of type void*. If you want to pass back a single int or
other small number, you can do this easily by casting the value to void* and then
casting back to the appropriate type after calling
pthread_join.
1
The program in Listing 4.4 computes the nth prime number in a separate thread.
That thread returns the desired prime number as its thread return value.The main
thread, meanwhile, is free to execute other code. Note that the successive division
algorithm used in compute_prime is quite inefficient; consult a book on numerical
algorithims if you need to compute many prime numbers in your programs.
Listing 4.3 Continued
1. Note that this is not portable, and it’s up to you to make sure that your value can be cast
safely to
void* and back without losing bits.
05 0430 CH04 5/22/01 10:21 AM Page 66
67
4.1 Thread Creation
Listing 4.4 ( primes.c) Compute Prime Numbers in a Thread
#include <pthread.h>
#include <stdio.h>
/* Compute successive prime numbers (very inefficiently). Return the
Nth prime number, where N is the value pointed to by *ARG. */
void* compute_prime (void* arg)
{
int candidate = 2;
int n = *((int*) arg);
while (1) {
int factor;
int is_prime = 1;
/* Test primality by successive division. */
for (factor = 2; factor < candidate; ++factor)
if (candidate % factor == 0) {
is_prime = 0;
break;
}
/* Is this the prime number we’re looking for? */
if (is_prime) {
if ( n == 0)
/* Return the desired prime number as the thread return value. */
return (void*) candidate;
}
++candidate;
}
return NULL;
}
int main ()
{
pthread_t thread;
int which_prime = 5000;
int prime;
/* Start the computing thread, up to the 5,000th prime number. */
pthread_create (&thread, NULL, &compute_prime, &which_prime);
/* Do some other work here */
/* Wait for the prime number thread to complete, and get the result. */
pthread_join (thread, (void*) &prime);
/* Print the largest prime it computed. */
printf(“The %dth prime number is %d.\n”, which_prime, prime);
return 0;
}
05 0430 CH04 5/22/01 10:21 AM Page 67
68
Chapter 4 Threads
4.1.4 More on Thread IDs
Occasionally, it is useful for a sequence of code to determine which thread is execut-
ing it.The pthread_self function returns the thread ID of the thread in which it is
called.This thread ID may be compared with another thread ID using the
pthread_equal function.
These functions can be useful for determining whether a particular thread ID
corresponds to the current thread. For instance, it is an error for a thread to call
pthread_join to join itself. (In this case, pthread_join would return the error code
EDEADLK.) To check for this beforehand, you might use code like this:
if (!pthread_equal (pthread_self (), other_thread))
pthread_join (other_thread, NULL);
4.1.5 Thread Attributes
Thread attributes provide a mechanism for fine-tuning the behavior of individual
threads. Recall that pthread_create accepts an argument that is a pointer to a thread
attribute object. If you pass a null pointer, the default thread attributes are used to
configure the new thread. However, you may create and customize a thread attribute
object to specify other values for the attributes.
To specify customized thread attributes, you must follow these steps:
1. Create a pthread_attr_t object.The easiest way is simply to declare an auto-
matic variable of this type.
2. Call pthread_attr_init, passing a pointer to this object.This initializes the
attributes to their default values.
3. Modify the attribute object to contain the desired attribute values.
4. Pass a pointer to the attribute object when calling pthread_create.
5. Call pthread_attr_destroy to release the attribute object.The pthread_attr_t
variable itself is not deallocated; it may be reinitialized with pthread_attr_init.
A single thread attribute object may be used to start several threads. It is not necessary
to keep the thread attribute object around after the threads have been created.
For most GNU/Linux application programming tasks, only one thread attribute is
typically of interest (the other available attributes are primarily for specialty real-time
programming).This attribute is the thread’s detach state. A thread may be created as a
joinable thread (the default) or as a detached thread. A joinable thread, like a process, is not
automatically cleaned up by GNU/Linux when it terminates. Instead, the thread’s exit
state hangs around in the system (kind of like a zombie process) until another thread
calls pthread_join to obtain its return value. Only then are its resources released. A
detached thread, in contrast, is cleaned up automatically when it terminates. Because a
detached thread is immediately cleaned up, another thread may not synchronize on its
completion by using pthread_join or obtain its return value.
05 0430 CH04 5/22/01 10:21 AM Page 68
69
4.2 Thread Cancellation
To set the detach state in a thread attribute object, use pthread_attr_setdetachstate.
The first argument is a pointer to the thread attribute object, and the second is the
desired detach state. Because the joinable state is the default, it is necessary to call this only
to create detached threads; pass PTHREAD_CREATE_DETACHED as the second argument.
The code in Listing 4.5 creates a detached thread by setting the detach state thread
attribute for the thread.
Listing 4.5 (detached.c) Skeleton Program That Creates a Detached Thread
#include <pthread.h>
void* thread_function (void* thread_arg)
{
/* Do work here */
}
int main ()
{
pthread_attr_t attr;
pthread_t thread;
pthread_attr_init (&attr);
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
pthread_create (&thread, &attr, &thread_function, NULL);
pthread_attr_destroy (&attr);
/* Do work here */
/* No need to join the second thread. */
return 0;
}
Even if a thread is created in a joinable state, it may later be turned into a detached
thread.To do this, call pthread_detach. Once a thread is detached, it cannot be made
joinable again.
4.2 Thread Cancellation
Under normal circumstances, a thread terminates when it exits normally, either by
returning from its thread function or by calling pthread_exit. However, it is possible
for a thread to request that another thread terminate.This is called canceling a thread.
To cancel a thread, call pthread_cancel, passing the thread ID of the thread to be
canceled. A canceled thread may later be joined; in fact, you should join a canceled
thread to free up its resources, unless the thread is detached (see Section 4.1.5,“Thread
Attributes”).The return value of a canceled thread is the special value given by
PTHREAD_CANCELED.
05 0430 CH04 5/22/01 10:21 AM Page 69
70
Chapter 4 Threads
Often a thread may be in some code that must be executed in an all-or-nothing
fashion. For instance, the thread may allocate some resources, use them, and then deal-
locate them. If the thread is canceled in the middle of this code, it may not have the
opportunity to deallocate the resources, and thus the resources will be leaked.To
counter this possibility, it is possible for a thread to control whether and when it can
be canceled.
A thread may be in one of three states with regard to thread cancellation.
n
The thread may be asynchronously cancelable.The thread may be canceled at any
point in its execution.
n
The thread may be synchronously cancelable.The thread may be canceled, but not
at just any point in its execution. Instead, cancellation requests are queued, and
the thread is canceled only when it reaches specific points in its execution.
n
A thread may be uncancelable.Attempts to cancel the thread are quietly ignored.
When initially created, a thread is synchronously cancelable.
4.2.1 Synchronous and Asynchronous Threads
An asynchronously cancelable thread may be canceled at any point in its execution. A
synchronously cancelable thread, in contrast, may be canceled only at particular places
in its execution.These places are called cancellation points.The thread will queue a can-
cellation request until it reaches the next cancellation point.
To make a thread asynchronously cancelable, use pthread_setcanceltype.This
affects the thread that actually calls the function.The first argument should be
PTHREAD_CANCEL_ASYNCHRONOUS to make the thread asynchronously cancelable, or
PTHREAD_CANCEL_DEFERRED to return it to the synchronously cancelable state.The sec-
ond argument, if not null, is a pointer to a variable that will receive the previous can-
cellation type for the thread.This call, for example, makes the calling thread
asynchronously cancelable.
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
What constitutes a cancellation point, and where should these be placed? The most
direct way to create a cancellation point is to call pthread_testcancel.This does
nothing except process a pending cancellation in a synchronously cancelable thread.
You should call pthread_testcancel periodically during lengthy computations in a
thread function, at points where the thread can be canceled without leaking any
resources or producing other ill effects.
Certain other functions are implicitly cancellation points as well.These are listed on
the pthread_cancel man page. Note that other functions may use these functions
internally and thus will indirectly be cancellation points.
05 0430 CH04 5/22/01 10:21 AM Page 70
71
4.2 Thread Cancellation
4.2.2 Uncancelable Critical Sections
A thread may disable cancellation of itself altogether with the
pthread_setcancelstate function. Like pthread_setcanceltype, this affects the calling
thread.The first argument is PTHREAD_CANCEL_DISABLE to disable cancellation, or
PTHREAD_CANCEL_ENABLE to re-enable cancellation.The second argument, if not null,
points to a variable that will receive the previous cancellation state.This call, for
instance, disables thread cancellation in the calling thread.
pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL);
Using pthread_setcancelstate enables you to implement critical sections. A critical sec-
tion is a sequence of code that must be executed either in its entirety or not at all; in
other words, if a thread begins executing the critical section, it must continue until the
end of the critical section without being canceled.
For example, suppose that you’re writing a routine for a banking program that
transfers money from one account to another.To do this, you must add value to the
balance in one account and deduct the same value from the balance of another
account. If the thread running your routine happened to be canceled at just the wrong
time between these two operations, the program would have spuriously increased the
bank’s total deposits by failing to complete the transaction.To prevent this possibility,
place the two operations in a critical section.
You might implement the transfer with a function such as
process_transaction,
shown in Listing 4.6.This function disables thread cancellation to start a critical sec-
tion before it modifies either account balance.
Listing 4.6 (critical-section.c) Protect a Bank Transaction with a Critical Section
#include <pthread.h>
#include <stdio.h>
#include <string.h>
/* An array of balances in accounts, indexed by account number. */
float* account_balances;
/* Transfer DOLLARS from account FROM_ACCT to account TO_ACCT. Return
0 if the transaction succeeded, or 1 if the balance FROM_ACCT is
too small. */
int process_transaction (int from_acct, int to_acct, float dollars)
{
int old_cancel_state;
/* Check the balance in FROM_ACCT. */
if (account_balances[from_acct] < dollars)
return 1;
continues
05 0430 CH04 5/22/01 10:21 AM Page 71
72
Chapter 4 Threads
/* Begin critical section. */
pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &old_cancel_state);
/* Move the money. */
account_balances[to_acct] += dollars;
account_balances[from_acct] -= dollars;
/* End critical section. */
pthread_setcancelstate (old_cancel_state, NULL);
return 0;
}
Note that it’s important to restore the old cancel state at the end of the critical section
rather than setting it unconditionally to PTHREAD_CANCEL_ENABLE.This enables you to
call the process_transaction function safely from within another critical section—in
that case, your function will leave the cancel state the same way it found it.
4.2.3 When to Use Thread Cancellation
In general, it’s a good idea not to use thread cancellation to end the execution of a
thread, except in unusual circumstances. During normal operation, a better strategy is
to indicate to the thread that it should exit, and then to wait for the thread to exit on
its own in an orderly fashion.We’ll discuss techniques for communicating with the
thread later in this chapter, and in Chapter 5,“Interprocess Communication.”
4.3 Thread-Specific Data
Unlike processes, all threads in a single program share the same address space.This
means that if one thread modifies a location in memory (for instance, a global vari-
able), the change is visible to all other threads.This allows multiple threads to operate
on the same data without the use interprocess communication mechanisms (which are
described in Chapter 5).
Each thread has its own call stack, however.This allows each thread to execute dif-
ferent code and to call and return from subroutines in the usual way. As in a single-
threaded program, each invocation of a subroutine in each thread has its own set of
local variables, which are stored on the stack for that thread.
Sometimes, however, it is desirable to duplicate a certain variable so that each
thread has a separate copy. GNU/Linux supports this by providing each thread with a
thread-specific data area.The variables stored in this area are duplicated for each thread,
and each thread may modify its copy of a variable without affecting other threads.
Because all threads share the same memory space, thread-specific data may not be
accessed using normal variable references. GNU/Linux provides special functions for
setting and retrieving values from the thread-specific data area.
Listing 4.6 Continued
05 0430 CH04 5/22/01 10:21 AM Page 72
73
4.3 Thread-Specific Data
You may create as many thread-specific data items as you want, each of type void*.
Each item is referenced by a key.To create a new key, and thus a new data item for
each thread, use pthread_key_create.The first argument is a pointer to a
pthread_key_t variable.That key value can be used by each thread to access its own
copy of the corresponding data item.The second argument to pthread_key_t is a
cleanup function. If you pass a function pointer here, GNU/Linux automatically calls
that function when each thread exits, passing the thread-specific value corresponding
to that key.This is particularly handy because the cleanup function is called even if the
thread is canceled at some arbitrary point in its execution. If the thread-specific value
is null, the thread cleanup function is not called. If you don’t need a cleanup function,
you may pass null instead of a function pointer.
After you’ve created a key, each thread can set its thread-specific value correspond-
ing to that key by calling
pthread_setspecific.The first argument is the key, and the
second is the void* thread-specific value to store.To retrieve a thread-specific data
item, call pthread_getspecific, passing the key as its argument.
Suppose, for instance, that your application divides a task among multiple threads.
For audit purposes, each thread is to have a separate log file, in which progress mes-
sages for that thread’s tasks are recorded.The thread-specific data area is a convenient
place to store the file pointer for the log file for each individual thread.
Listing 4.7 shows how you might implement this.The main function in this sample
program creates a key to store the thread-specific file pointer and then stores it in
thread_log_key. Because this is a global variable, it is shared by all threads.When each
thread starts executing its thread function, it opens a log file and stores the file pointer
under that key. Later, any of these threads may call write_to_thread_log to write a
message to the thread-specific log file.That function retrieves the file pointer for the
thread’s log file from thread-specific data and writes the message.
Listing 4.7 (tsd.c) Per-Thread Log Files Implemented with Thread-Specific Data
#include <malloc.h>
#include <pthread.h>
#include <stdio.h>
/* The key used to associate a log file pointer with each thread. */
static pthread_key_t thread_log_key;
/* Write MESSAGE to the log file for the current thread. */
void write_to_thread_log (const char* message)
{
FILE* thread_log = (FILE*) pthread_getspecific (thread_log_key);
fprintf (thread_log, “%s\n”, message);
}
/* Close the log file pointer THREAD_LOG. */
void close_thread_log (void* thread_log)
continues
05 0430 CH04 5/22/01 10:21 AM Page 73
74
Chapter 4 Threads
{
fclose ((FILE*) thread_log);
}
void* thread_function (void* args)
{
char thread_log_filename[20];
FILE* thread_log;
/* Generate the filename for this thread’s log file. */
sprintf (thread_log_filename, “thread%d.log”, (int) pthread_self ());
/* Open the log file. */
thread_log = fopen (thread_log_filename, “w”);
/* Store the file pointer in thread-specific data under thread_log_key. */
pthread_setspecific (thread_log_key, thread_log);
write_to_thread_log (“Thread starting.”);
/* Do work here */
return NULL;
}
int main ()
{
int i;
pthread_t threads[5];
/* Create a key to associate thread log file pointers in
thread-specific data. Use close_thread_log to clean up the file
pointers. */
pthread_key_create (&thread_log_key, close_thread_log);
/* Create threads to do the work. */
for (i = 0; i < 5; ++i)
pthread_create (&(threads[i]), NULL, thread_function, NULL);
/* Wait for all threads to finish. */
for (i = 0; i < 5; ++i)
pthread_join (threads[i], NULL);
return 0;
}
Observe that thread_function does not need to close the log file.That’s because when
the log file key was created, close_thread_log was specified as the cleanup function
for that key.Whenever a thread exits, GNU/Linux calls that function, passing the
thread-specific value for the thread log key.This function takes care of closing the
log file.
Listing 4.7 Continued
05 0430 CH04 5/22/01 10:21 AM Page 74
Không có nhận xét nào:
Đăng nhận xét