Книга: Programming with POSIX® Threads

2.1 Creating and using threads

2.1 Creating and using threads

"A loaf of bread," the Walrus said,

"Is what we chiefly need:

Pepper and vinegar besides

Are very good indeed—

ow, if you're ready, Oysters dear,

We can begin to feed."

Lewis Carroll, Through the Looking-Glass

pthread_t thread;

int pthread_equal (pthread_t tl, pthread_t t2);

int pthread_create (pthread_t *thread,

const pthread_attr_t *attr,

void *(*start)(void *), void *arg);

pthread_t pthread_self (void);

int sched_yield (void);

int pthread_exit (void *value_ptr);

int pthread_detach (pthread_t thread);

int pthread_join (pthread_t thread, void **value_ptr);

The introduction covered some of the basics of what a thread is, and what it means to the computer hardware. This section begins where the introduction left off. It explains how a thread is represented in your program, what it means to your program, and some of the operations you can perform on threads. If you haven't read the introduction, this would be a good time to skip back to it. (I'll wait for you here.)

Within your program a thread is represented by a thread identifier, of the opaque type pthread_t. To create a thread, you must declare a variable of type pthread_t somewhere in your program. If the identifier is needed only within a function, or if the function won't return until the thread is done, you could declare the identifier with auto storage class. Most of the time, though, the identifier will be stored in a shared (static or extern) variable, or in a structure allocated from the heap.

A Pthreads thread begins by calling some function that you provide. This "thread function" should expect a single argument of type void *, and should return a value of the same type. You create a thread by passing the thread function's address, and the argument value with which you want the function to be called, to pthread_create.

When you create a thread, pthread_create returns an identifier, in the pthread_t value referred to by the thread argument, by which your code refers to the new thread. A thread can also get its own identifier using the pthread_self function. There is no way to find a thread's identifier unless either the creator or the thread itself stores the identifier somewhere. You need to have a thread's identifier to do anything to the thread. If you'll need to know when a thread completes, for example, you must keep the identifier somewhere.

Pthreads provides the pthread_equal function to compare two thread identifiers. You can only test for equality. It doesn't make any sense to ask whether one thread identifier is "greater than" or "less than" another, because there is no ordering between threads. The pthread_equal function returns a nonzero value if the thread identifiers refer to the same thread, and the value 0 if they do not refer to the same thread.

The initial thread (main) is special.

When a C program runs, it begins in a special function named main. In a threaded program, this special stream of execution is called the "initial thread" or sometimes the "main thread." You can do anything within the initial thread that you can do within any other thread. It can determine its own thread identifier by calling pthread_self, for example, or terminate itself by calling pthread_exit. If the initial thread stores its thread identifier somewhere accessible to another thread, that thread can wait for the initial thread to terminate, or detach the initial thread.

The initial thread is special because Pthreads retains traditional UNIX process behavior when the function main returns; that is, the process terminates without allowing other threads to complete. In general, you do not want to do this in a threaded program, but sometimes it can be convenient. In many of the programs in this book, for example, threads are created that have no effect on anything outside the process. It doesn't really matter what those threads are doing, then, if the process goes away. When the process exits, those threads, all their states, and anything they might accomplish, simply "evaporate"—there's no reason to clean up.

Detaching a thread that is still running doesn't affect the thread in any way—it just informs the system that the thread's resources can be reclaimed when the thread eventually terminates.

Although "thread evaporation" is sometimes useful, most of the time your process will outlive the individual threads you create. To be sure that resources used by terminated threads are available to the process, you should always detach each thread you create when you're finished with it. Threads that have terminated but are not detached may retain virtual memory, including their stacks, as well as other system resources. Detaching a thread tells the system that you no longer need that thread, and allows the system to reclaim the resources it has allocated to the thread.

If you create a thread that you will never need to control, you can use an attribute to create the thread so that it is already detached. (We'll get to attributes later, in Section 5.2.3.) If you do not want to wait for a thread that you created, and you know that you will no longer need to control that thread, you can detach it at any time by calling pthread_detach. A thread may detach itself, or any other thread that knows its pthread_t identifier may detach it at any time. If you need to know a thread's return value, or if you need to know when a thread has completed, call pthread_join. The pthread_join function will block the caller until the thread you specify has terminated, and then, optionally, store the terminated thread's return value. Calling pthread_join detaches the specified thread automatically.

As we've seen, threads within a process can execute different instructions, using different stacks, all at the same time. Although the threads execute independently of each other, they always share the same address space and file

descriptors. The shared address space provides an important advantage of the threaded programming model by allowing threads to communicate efficiently.

Some programs may create threads that perform unrelated activities, but most often a set of threads works together toward a common goal. For example, one set of threads may form an assembly line in which each performs some specific task on a shared data stream and then passes the data on to the next thread. A set of threads may form a work crew and divide independent parts of a common task. Or one "manager" thread may take control and divide work among a "crew" of worker threads. You can combine these models in a variety of ways; for example, a work crew might perform some complicated step in a pipeline, such as transforming a slice of an array.

The following program, lifecycle.c, creates a thread. We'll refer to this simple example in the following sections about a thread's life cycle.

7-10 The thread function, thread_routine, returns a value to satisfy the standard thread function prototype. In this example the thread returns its argument, and the value is always NULL.

18-25 The program creates a thread by calling pthread_create, and then waits for it by calling pthread_join. You don't need to wait for a thread, but if you don't, you'll need to do something else to make sure the process runs until the thread completes. Returning from main will cause the process to terminate, along with all threads. You could, for example, code the main thread to terminate by calling pthread_exit, which would allow the process to continue until all threads have terminated.

26-29 When the join completes, the program checks the thread's return value, to be sure that the thread returned the value it was given. The program exits with 0 (success) if the value is NULL, or with 1 otherwise.

It is a good idea for all thread functions to return something, even if it is simply NULL. If you omit the return statement, pthread_join will still return some value—whatever happens to be in the place where the thread's start function would have stored a return value (probably a register).

? lifecycle.c

1 #include <pthread.h>
2 #include "errors.h" 3
4 /*
5 * Thread start routine.
6 */
7 void *thread_routine (void *arg)
8 {
9 return arg; 10 }
11
12 main (int argc, char *argv[])
13 {
14 pthread_t thread_id;
15 void *thread result;
16 int status;
17
18 status = pthread_create (
19 &thread_id, NULL, thread_routine, NULL);
20 if (status != 0)
21  err_abort (status, "Create thread");

22
23 status = pthread_join (thread_id, &thread_result);
24 if (status != 0)
25  err_abort (status, "Join thread");
26 if (thread_result == NULL)

27  return 0;
28 else
29  return 1;
30 }

If the "joining" thread doesn't care about the return value, or if it knows that the "joinee" (the thread with which it is joining) didn't return a value, then it can pass NULL instead of &retval in the call to pthread_join. The joinee's return value will be ignored.

When the call to pthread_join returns, the joinee has been detached and you can't join with it again. In the rare cases where more than one thread might need to know when some particular thread has terminated, the threads should wait on a condition variable instead of calling pthread_join. The terminating thread would store its return value (or any other information) in some known location, and broadcast the condition variable to wake all threads that might be interested.

Оглавление книги

Оглавление статьи/книги

Генерация: 1.370. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз