Êíèãà: Programming with POSIX® Threads
5.3.3 Cleaning up
5.3.3 Cleaning up
When you write any library code, design it to handle deferred cancellation gracefully. Disable cancellation where it is not appropriate,and always use cleanup handlers at cancellation points.
If a section of code needs to restore some state when it is canceled, it must use cleanup handlers. When a thread is canceled while waiting for a condition variable, it will wake up with the mutex locked. Before the thread terminates it usually needs to restore invariants, and it always needs to release the mutex.
Each thread may be considered to have a stack of active cleanup handlers. Cleanup handlers are added to the stack by calling pthread_cleanup_push, and the most recently added cleanup handler is removed by calling pthread_cleanup_ pop. When the thread is canceled or when it exits by calling pthread_exit, Pthreads calls each active cleanup handler in turn, beginning with the most recently added cleanup handler. When all active cleanup handlers have returned, the thread is terminated.
Pthreads cleanup handlers are designed so that you can often use the cleanup handler even when the thread wasn't canceled. It is often useful to run the same cleanup function regardless of whether your code is canceled or completes normally. When pthread_cleanup_pop is called with a nonzero value, the cleanup handler is executed even ifthe thread was not canceled.
You cannot push a cleanup handler in one function and pop it in another function. The pthread_cleanup_push and pthread_cleanup_pop operations may be defined as macros, such that pthread_cleanup_push contains the opening brace "{" of ablock, while pthread_cleanup_pop contains the matching closing brace "}" of the block. You must always keep this restriction in mind while using cleanup handlers, if you wish your code to be portable.
The following program, cancel_cleanup.c, shows the use of a cleanup handler to release a mutex when a condition variable wait is canceled.
10-17 The control structure (control) is used by all threads to maintain shared synchronization objects and invariants. Each thread increases the member counter by one when it starts, and decreases it at termination. The member busy is used as a dummy condition wait predicate — it is initialized to 1, and never cleared, which means that the condition wait loops will never terminate (in this example) until the threads are canceled.
24-34 The function cleanup_handler is installed as the cancellation cleanup handler for each thread. It is called on normal termination as well as through cancellation, to decrease the count of active threads and unlock the mutex.
47 The function thread_routine establishes cleanup_handler as the active cancellation cleanup handler.
54-58 Wait until the control structure's busy member is set to 0, which, in this example, will never occur. The condition wait loop will exit only when the wait is canceled.
60 Although the condition wait loop in this example will not exit, the function cleans up by removing the active cleanup handler. The nonzero argument to pthread_cleanup_pop
, remember, means that the cleanup handler will be called even though cancellation did not occur.
In some cases, you may omit "unreachable statements" like this pthread_cleanup_pop
call. However, in this case, your code might not compile without it. The pthread_cleanup_push
and pthread_cleanup_pop
macros are special, and may expand to form, respectively, the beginning and ending of a block. Digital UNIX does this, for example, to implement cancellation on top of the common structured exception handling provided by the operating system.
? cancel_cleanup.c
1 #include <pthread.h>
2 #include "errors.h"
3
4 #define THREADS 5
5
6 /*
7 * Control structure shared by the test threads, containing
8 * the synchronization and invariant data.
9 */
10 typedef struct control_tag {
11 int counter, busy;
12 pthread_mutex_t mutex;
13 pthread_cond_t cv;
14 } control_t;
15
16 control_t control =
17 {0, 1, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};
18
19 /*
20 * This routine is installed as the cancellation cleanup
21 * handler around the cancelable condition wait. It will
22 * be called by the system when the thread is canceled.
23 */
24 void cleanup_handler (void *arg)
25 {
26 control_t *st = (control_t *)arg;
27 int status;
28
29 st->counter--;
30 printf ("cleanup_handler: counter == %dn", st->counter);
31 status = pthread_mutex_unlock (&st->mutex);
32 if (status != 0)
33 err_abort (status, "Unlock in cleanup handler");
34 }
35
36 /*
37 * Multiple threads are created running this routine (controlled
38 * by the THREADS macro). They maintain a "counter" invariant,
39 * which expresses the number of running threads. They specify a
40 * nonzero value to pthread_cleanup_pop to run the same
41 * "finalization" action when cancellation does not occur.
42 */
43 void *thread_routine (void *arg)
44 {
45 int status;
46
47 pthread_cleanup_push (cleanup_handler, (void*)&control);
48
49 status = pthread_mutex_lock (&control.mutex);
50 if (status != 0)
51 err_abort (status, "Mutex lock");
52 control.counter++;
53
54 while (control.busy) {
55 status = pthread_cond_wait (&control.cv, &control.mutex);
56 if (status != 0)
57 err_abort (status, "Wait on condition");
58 }
59
60 pthread_cleanup_pop (1);
61 return NULL;
62 }
63
64 int main (int argc, char *argv[])
65 {
66 pthread_t thread_id[THREADS];
67 int count;
68 void *result;
69 int status;
70
71 for (count = 0; count < THREADS; count++) {
72 status = pthread_create (
73 &thread_id[count], NULL, thread_routine, NULL);
74 if (status != 0)
75 err_abort (status, "Create thread");
76 }
77
78 sleep (2);
79
80 for (count = 0; count < THREADS; count++) {
81 status = pthread_cancel (thread_id[count]);
82 if (status != 0)
83 err_abort (status, "Cancel thread"); 84
85 status = pthread_join (thread_id[count], &result);
86 if (status != 0)
87 err_abort (status, "Join thread");
88 if (result == PTHREAD_CANCELED)
89 printf ("thread %d canceledn", count);
90 else
91 printf ("thread %d was not canceledn", count);
92 }
93 return 0;
If one of your threads creates a set of threads to "subcontract" some function, say, a parallel arithmetic operation, and the "contractor" is canceled while the function is in progress, you probably won't want to leave the subcontractor threads running. Instead, you could "pass on" the cancellation to each subcontrator thread, letting them handle their own termination independently.
If you had originally intended to join with the subcontractors, remember that they will continue to consume some resources until they have been joined or detached. When the contractor thread cancels them, you should not delay cancellation by joining with the subcontractors. Instead, you can cancel each thread and immediately detach it using pthread_detach. The subcontractor resources can then be recycled immediately as they finish, while the contractor can wrap things up independently.
The following program, cancel_subcontract.c, shows one way to propagate cancellation to subcontractors.
9-12 The team_t
structure defines the state of the team of subcontractor threads. The join_i
member records the index of the last subcontractor with which the contractor had joined, so on cancellation from within pthread_join, it can cancel the threads it had not yet joined. The workers member is an array recording the thread identifiers of the subcontractor threads.
18-25 The subcontractor threads are started running the worker_routine function. This function loops until canceled, calling pthread_testcancel every 1000 iterations.
31-46 The cleanup function is established as the active cleanup handler within the contractor thread. When the contractor is canceled, cleanup iterates through the remaining (unjoined) subcontractors, cancelling and detaching each. Note that it does not join the subcontractors — in general, it is not a good idea to wait in a cleanup handler. The thread, after all, is expected to clean up and terminate, not to wait around for something to happen. But if your cleanup handler really needs to wait for something, don't be afraid, it will workjust fine.
53-76 The contractor thread is started running thread_routine. This function creates a set of subcontractors, then joins with each subcontractor. As it joins each thread, it records the current index within the workers array in the team_t member join_i. The cleanup handler is established with a pointer to the team structure so that it can determine the last offset and begin cancelling the remaining subcontractors.
78-104 The main program creates the contractor thread, running thread_routine, and then sleeps for five seconds. When it wakes up, it cancels the contractor thread, and waits for it to terminate.
? cancel_subcontract.c
1 #include <pthread.h>
2 #include "errors.h" 3
4 #define THREADS 5
5
6 /*
7 * Structure that defines the threads in a "team."
8 */
9 typedef struct team_tag {
10 int join_i; /* join index */
11 pthread_t workers[THREADS]; /* thread identifiers */
12 } team_t;
13
14 /*
15 * Start routine for worker threads. They loop waiting for a
16 * cancellation request.
17 */
18 void *worker_routine (void *arg)
19 {
20 int counter;
21
22 for (counter = 0; ; counter++)
23 if ((counter % 1000) == 0)
24 pthread_testcancel ();
25 }
26
27 /*
28 * Cancellation cleanup handler for the contractor thread. It
29 * will cancel and detach each worker in the team.
30 */
31 void cleanup (void *arg)
32 {
33 team_t *team = (team_t *)arg;
34 int count, status;
35
36 for (count = team->join_i; count < THREADS; count++) {
37 status = pthread_cancel (team->workers[count]);
38 if (status != 0)
39 err_abort (status, "Cancel worker");
40
41 status = pthread_detach(team->workers[count]);
42 if (status != 0)
43 err_abort (status, "Detach worker");
44 printf ("Cleanup: canceled %dn", count);
45 }
46 }
47
48 /*
49 * Thread start routine for the contractor. It creates a team of
50 * worker threads, and then joins with them. When canceled, the
51 * cleanup handler will cancel and detach the remaining threads.
52 */
53 void *thread_routine (void *arg)
54 {
53 team_t team; /* team info */
56 int count;
57 void *result; /* Return status */
58 int status;
59
60 for (count = 0; count < THREADS; count++) {
61 status = pthread_create (
62 &team.workers[count], NULL, worker_routine, NULL);
63 if (status != 0)
64 err_abort (status, "Create worker");
55 }
66 pthread_cleanup_push (cleanup, (void*)&team);
67
68 for (team.join_i = 0; team.join_i < THREADS; team.join_i++) {
69 status = pthread_join (team.workers[team.join_i], &result);
70 if (status != 0)
71 err_abort (status, "Join worker");
72 }
73
74 pthread_cleanup_pop (0);
75 return NULL;
76 }
77
78 int main (int argc, char *argv[])
79 {
80 pthread_t thread_id;
81 int status;
82
83 #ifdef sun
84 /*
85 * On Solaris 2.5, threads are not timesliced. To ensure
86 * that our threads can run concurrently, we need to
87 * increase the concurrency level to at least 2 plus THREADS
88 * (the number of workers).
89 */
90 DPRINTF (("Setting concurrency level to %dn", THREADS+2));
91 thr_setconcurrency (THREADS+2);
92 #endif
93 status = pthread_create (&thread_id, NULL, thread_routine, NULL);
94 if (status != 0)
95 err_abort (status, "Create team");
96 sleep (5);
97 printf ("Cancelling...n");
98 status = pthread_cancel (thread_id);
99 if (status != 0)
100 err_abort (status, "Cancel team");
101 status = pthread_join (thread_id, NULL);
102 if (status != 0)
103 err_abort (status, "Join team");
104 }