Êíèãà: Programming with POSIX® Threads

5.3.2 Asynchronous cancelability

5.3.2 Asynchronous cancelability

Asynchronous cancellation is useful because the "target thread" doesn't need to poll for cancellation requests by using cancellation points. That can be valuable for a thread that runs a tight compute-bound loop (for example, searching for a prime number factor) where the overhead of calling pthread_testcancel might be severe.

Avoid asynchronous cancellation! It is difficult to use correctly and is rarely useful.

The problem is that you're limited in what you can do with asynchronous cancellation enabled. You can't acquire any resources, for example, including locking a mutex. That's because the cleanup code would have no way to determine

whether the mutex had been locked. Asynchronous cancellation can occur at any hardware instruction. On some computers it may even be possible to interrupt some instructions in the middle. That makes it really difficult to determine what the canceled thread was doing.

For example, when you call malloc the system allocates some heap memory for you, stores a pointer to that memory somewhere (possibly in a hardware register), and then returns to your code, which probably moves the return value into some local storage for later use. There are lots of places that malloc might be interrupted by an asynchronous cancel, with varying effects. It might be interrupted before the memory was allocated. Or it might be interrupted after allocating storage but before it stored the address for return. Or it might even return to your code, but get interrupted before the return value could be copied to a local variable. In any of those cases the variable where your code expects to find a pointer to the allocated memory will be uninitialized. You can't tell whether the memory really was allocated yet. You can't free the memory, so that memory (if it was allocated to you) will remain allocated for the life of the program. That's a memory leak, which is not a desirable feature.

Or when you call pthread_mutex_lock, the system might be interrupted within a function call either before or after locking the mutex. Again, there's no way for your program to find out, because the interrupt may have occurred between any two instructions, even within the pthread_mutex_lock function, which might leave the mutex unusable. If the mutex is locked, the application will likely end up hanging because it will never be unlocked.

Call no code with asynchronous cancellation enabled unless you wrote it to be async-cancel safe — and even then, think twice!

You are not allowed to call any function that acquires resources while asynchronous cancellation is enabled. In fact, you should never call any function while asynchronous cancellation is enabled unless the function is documented as "async-cancel safe." The only functions required to be async safe by Pthreads are pthread_cancel, pthread_setcancelstate, and pthread_setcanceltype. (And there is no reason to call pthread_cancel with asynchronous cancelability enabled.) No other POSIX orANSI C functions need be async-cancel safe, and you should never call them with asynchronous cancelability enabled.

Pthreads suggests that all library functions should document whether or not they are async-cancel safe. However if the description of a function does not specifically say it is async-cancel safe you should always assume that it is not. The consequences of asynchronous cancellation in a function that is not async-cancel safe can be severe. And worse, the effects are sensitive to timing — so a function that appears to be async-cancel safe during experimentation may in fact cause all sorts of problems later when it ends up being canceled in a slightly different place.

The following program, cancel_async.c, shows the use of asynchronous cancellation in a compute-bound loop. Use of asynchronous cancellation makes this

loop "more responsive" than the deferred cancellation loop in cancel.c. However, the program would become unreliable if any function calls were made within the loop, whereas the deferred cancellation version would continue to function correctly. In most cases, synchronous cancellation is preferable.

24-28 To keep the thread running awhile with something more interesting than an empty loop, cancel_async.c uses a simple matrix multiply nested loop. The matrixa and matrixb arrays are initialized with, respectively, their major or minor array index.

34-36 The cancellation type is changed to PTHREAD_CANCEL_ASYNCHRONOUS, allowing asynchronous cancellation within the matrix multiply loops.

39-44 The thread repeats the matrix multiply until canceled, on each iteration replacing the first source array (matrixa) with the result of the previous multiplication (matrixc).

66-74 Once again, on a Solaris system, set the thread concurrency level to 2, allowing the main thread and thread_routine to run concurrently on a uniprocessor. The program will hang without this step, since user mode threads are not timesliced on Solaris.

? cancel_async.c

1 #include <pthread.h>
2 #include "errors.h" 3
4 #define SIZE 10 /* array size */
5
6 static int matrixa[SIZE][SIZE];
7 static int matrixb[SIZE][SIZE];
8 static int matrixc[SIZE][SIZE]; 9
10 /*
11 * Loop until canceled. The thread can be canceled at any
12 * point within the inner loop, where asynchronous cancellation
13 * is enabled. The loop multiplies the two matrices matrixa
14 * and matrixb.
15 */
16 void *thread_routine (void *arg)
17 {
18 int cancel_type, status;
19 int i, j, k, value = 1;

20
21 /*
22 * Initialize the matrices to something arbitrary.
23 */
24 for (i = 0; i < SIZE; i++)
25 for (j = 0; j < SIZE; j++) {
26 matrixa[i][j] = i;
27 matrixb[i][j] = j;
28 }

29
30 while (1) {
31 /*
32 * Compute the matrix product of matrixa and matrixb.
33 */
34 status = pthread_setcanceltype (
35 PTHREAD_CANCEL_ASYNCHRONOUS,
36 &cancel_type);
37 if (status != 0)
38 err_abort (status, "Set cancel type");
39 for (i = 0; i < SIZE; i++)
40 for (j = 0; j < SIZE; j++) {
41 matrixc[i][j] = 0;
42 for (k = 0; k < SIZE; k++)
43 matrixc[i][j] += matrixa[i][k] * matrixb[k][j];
44 }
45 status = pthread_setcanceltype (
46 cancel_type,
47 &cancel_type);
48 if (status != 0)
49 err_abort (status, "Set cancel type");
50
51 /*
52 * Copy the result (matrixc) into matrixa to start again
53 */
54 for (i = 0; i < SIZE; i++)
55 for (j = 0; j < SIZE; j++)
56 matrixa[i][j] = matrixc[i][j];
57 }
58 }

59
60 int main (int argc, char *argv[])
61 {
62 pthread_t thread_id;
63 void *result;
64 int status; 65
66 #ifdef sun
67 /*
68 * On Solaris 2.5, threads are not timesliced. To ensure
69 * that our two threads can run concurrently, we need to
70 * increase the concurrency level to 2.
71 */
72 DPRINTF (("Setting concurrency level to 2n"));
73 thr_setconcurrency (2);
74 #endif
75 status = pthread_create (
76 &thread_id, NULL, thread_routine, NULL);
77 if (status != 0)
78 err_abort (status, "Create thread");
79 sleep (1);
80 status = pthread_cancel (thread_id);
81 if (status != 0)
82 err_abort (status, "Cancel thread");
83 status = pthread_join (thread_id, &result);
84 if (status != 0)
85 err_abort (status, "Join thread");
86 if (result == PTHREAD_CANCELED)
87 printf ("Thread canceledn");
88 else
89 printf ("Thread was not canceledn");
90 return 0;
91 }

Warning: do not let "DCE threads'" habits carry over to Pthreads!

I'll end this section with a warning. DCE threads, a critical component of the Open Software Foundation's Distributed Computing Environment, was designed to be independent of the underlying UNIX kernel. Systems with no thread support at all often emulated "thread synchronous" I/O in user mode, using nonblocking I/O mode, so that a thread attempting I/O on a busy file was blocked on a condition variable until a later select or poll showed that the I/O could complete. DCE listener threads might block indefinitely on a socket read, and it was important to be able to cancel that read.

When DCE was ported to newer kernels that had thread support, but not Pthreads support, the user mode I/O wrappers were usually omitted, resulting in a thread blocked within a kernel that did not support deferred cancellation. Users discovered that, in many cases, these systems implemented asynchronous cancellation in such a way that, quite by coincidence, a kernel wait might be canceled "safely" if the thread switched to asynchronous cancellation immediately before the kernel call, and switched back to deferred cancellation immediately after. This observation was publicized in DCE documentation, but it is a very dangerous hack, even on systems where it seems to work. You should never try this on any Pthreads system! If your system conforms to POSIX 1003. 1c-1995 (or POSIX 1003.1, 1996 edition, or later), it supports deferred cancellation of, at minimum, kernel functions such as read and write. You do not need asynchronous cancellation, and using it can be extremely dangerous.

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


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