Êíèãà: Programming with POSIX® Threads

5.4.3 Using destructor functions

5.4.3 Using destructor functions

When a thread exits while it has a value defined for some thread-specific data key, you usually need to do something about it. If your key's value is a pointer to heap memory, you will need to free the memory to avoid a memory leak each time a thread terminates. Pthreads allows you to define a destructor function when you create a thread-specific data key. When a thread terminates with a non-NULL value for a thread-specific data key, the key's destructor (if any) is called with the current value of the key.

Thread-specific data destructors are called in "unspecified order."

Pthreads checks all thread-specific data keys in the process when a thread exits, and for each thread-specific data key with a value that's not NULL, it sets the value to NULL and then calls the key's destructor function. Going back to our analogy, someone might collect the identity badges of all programmers by removing whatever is hanging from each programmer's left shirt pocket, safe in the knowledge that it will always be the programmer's badge. Be careful, because the order in which destructors are called is undefined. Try to make each destructor as independent as possible.

Thread-specific data destructors can set a new value for the key for which a value is being destroyed or for any other key. You should never do this directly, but it can easily happen indirectly if you call other functions from your destructor. For example, the ANSI C library's destructors might be called before yours— and calling an ANSI C function, for example, using fprintf to write a log message to a file, could cause a new value to be assigned to a thread-specific data key. The system must recheck the list of thread-specific data values for you after all destructors have been called.

If your thread-specific data destructor creates a new thread-specific data value,you will get another chance. Maybe too many chances!

The standard requires that a Pthreads implementation may recheck the list some fixed number of times and then give up. When it gives up, the final thread-specific data value is not destroyed. If the value is a pointer to heap memory, the result may be a memory leak, so be careful. The <limits.h> header defines _PTHREAD_DESTRUCTOR_ITERATIONS to the number of times the system will check, and the value must be at least 4. Alternately, the system is allowed to keep checking forever, so a destructor function that always sets thread-specific data values may cause an infinite loop.

Usually, new thread-specific data values are set within a destructor only when subsystem 1 uses thread-specific data that depends on another independent subsystem 2 that also uses thread-specific data. Because the order in which destructor functions run is unspecified, the two may be called in the wrong order. If the subsystem 1 destructor needs to call into subsystem 2, it may inadvertently result in allocating new thread-specific data for subsystem 2. Although the subsystem 2 destructor will need to be called again to free the new data, the subsystem 1 thread-specific data remains NULL, so the loop will terminate.

The following program, tsd_destructor.c, demonstrates using thread-specific data destructors to release memory when a thread terminates. It also keeps track of how many threads are using the thread-specific data, and deletes the thread-speciflc data key when the destructor is run for the final thread. This program is similar in structure to tsd_once.c, from Section 5.3, so only the relevant differences will be annotated here.

12-14 In addition to the key value (identity_key), the program maintains a count of threads that are using the key (identity_key_counter), which is protected by a mutex (identity_key_mutex).

22-42 The function identity_key_destructor is the thread-specific data key's destructor function. It begins by printing a message so we can observe when it runs in each thread. It frees the storage used to maintain thread-specific data, the private_t structure. Then it locks the mutex associated with the thread-specific data key (identity_key_mutex) and decreases the count of threads using the key. If the count reaches 0, it deletes the key and prints a message.

48-63 The function identity_key_get can be used anywhere (in this example, it is used only once per thread) to get the value of identity_key for the calling thread. If there is no current value (the value is NULL), then it allocates a new private_t structure and assigns it to the key for future reference.

68-78 The function thread_routine is the thread start function used by the example. It acquires a value for the key by calling identity_key_get, and sets the members of the structure. The string member is set to the thread's argument, creating a global "name" for the thread, which can be used for printing messages. 80-114 The main program creates the thread-specific data key tsd_key. Notice that, unlike tsd_once. c, this program does not bother to use pthread_once. As I mentioned in the annotation for that example, in a main program it is perfectly safe, and more efficient, to create the key inside main, before creating any threads, 101 The main program initializes the reference counter (identity_key_counter) to 3. It is critical that you define in advance how many threads will reference a key that will be deleted based on a reference count, as we intend to do. The counter must be set before any thread using the key can possibly terminate.

You cannot, for example, code identity_key_get so that it dynamically increases the counter when it first assigns a thread-specific value for identity_ key. That is because one thread might assign a thread-specific value for identity_key and then terminate before another thread using the key had a chance to start. If that happened, the first thread's destructor would find no remaining references to the key, and it would delete the key. Later threads would then fail when trying to set thread-specific data values.

? tsd_destructor.c

1 #include <pthread.h>
2 #include "errors.h"
3
4 /*
5 * Structure used as value of thread-specific data key.
6 */
7 typedef struct private_tag {
8  pthread_t thread_id;
9  char *string;

10 } private_t;
11
12 pthread_key_t identity_key; /* Thread-specific data key */
13 pthread_mutex_t identity_key_mutex = PTHREAD_MUTEX_INITIALIZER;
14 long identity_key_counter = 0; 15
16 /*
17 * This routine is called as each thread terminates with a value
18 * for the thread-specific data key. It keeps track of how many
19 * threads still have values, and deletes the key when there are
20 * no more references.
21 */
22 void identity_key_destructor (void *value)
23 {
24 private_t *private = (private_t*)value;
25 int status;

26
27 printf ("thread "%s" exiting...n", private->string);
28 free (value);
29 status = pthread_mutex_lock (&identity_key_mutex);
30 if (status != 0)
31  err_abort (status, "Lock key mutex");
32 identity_key_counter--;
33 if (identity_key_counter <= 0) {
34  status = pthread_key_delete (identity_key);
35  if (status != 0)
36  err_abort (status, "Delete key");
37  printf ("key deleted...n");
38 }
39 status = pthread_mutex_unlock (&identity_key_mutex);
40 if (status != 0)
41  err_abort (status, "Unlock key mutex");
42 }

43
44 /*
45 * Helper routine to allocate a new value for thread-specific
46 * data key if the thread doesn't already have one.
47 */
48 void *identity_key_get (void)
49 {
50 void *value;
51 int status; 52
53 value = pthread_getspecific (identity_key);
54 if (value == NULL) {
55  value = malloc (sizeof (private_t));
56  if (value == NULL)
57  errno_abort ("Allocate key value");
58  status = pthread_setspecific (identity_key, (void*)value);
59  if (status != 0)
60  err_abort (status, "Set TSD");
61 }
62 return value;
63 }

64
65 /*
66 * Thread start routine to use thread-specific data.
67 */
68 void *thread_routine (void *arg)
69 {
70 private_t *value; 71
72 value = (private_t*)identity_key_get ();
73 value->thread_id = pthread_self ();
74 value->string = (char*)arg;
75 printf ("thread "%s" starting...n", value->string);
76 sleep (2);
77 return NULL;
78 }
79
80 void main (int argc, char *argv[])
81 {
82 pthread_t thread_l, thread_2;
83 private_t *value;
84 int status;

85
86 /*
87 * Create the TSD key, and set the reference counter to
88 * the number of threads that will use it (two thread_routine
89 * threads plus main). This must be done before creating
90 * the threads! Otherwise, if one thread runs the key's
91 * destructor before any other thread uses the key, it will
92 * be deleted.
93 *
94 * Note that there's rarely any good reason to delete a
95 * thread-specific data key.
96 */
97 status = pthread_key_create (
98  &identity_key, identity_key_destructor);
99 if (status != 0)
100  err_abort (status, "Create key");
101 identity_key_counter = 3;
102 value = (private_t*)identity_key_get ();
103 value->thread_id = pthread_self ();
104 value->string = "Main thread";
105 status = pthread_create (&thread_1, NULL,
106 thread_routine, "Thread 1");
107 if (status != 0)
108  err_abort (status, "Create thread 1");
109 status = pthread_create (&thread_2, NULL,
110  thread_routine, "Thread 2");
111 if (status != 0)
112  err_abort (status, "Create thread 2");
113 pthread_exit (NULL);
114 }

Îãëàâëåíèå êíèãè


Ãåíåðàöèÿ: 1.082. Çàïðîñîâ Ê ÁÄ/Cache: 3 / 0
ïîäåëèòüñÿ
Ââåðõ Âíèç