Êíèãà: Programming with POSIX® Threads

6.6.3 pthread_kill

6.6.3 pthread_kill

int pthread_kill (pthread_t thread, int sig);

Within a process, one thread can send a signal to a specific thread (including itself) by calling pthread_kill. When calling pthread_kill, you specify not only the signal number to be delivered, but also the pthread_t identifier for the thread to which you want the signal sent. You cannot use pthread_kill to send a signal to a thread in another process, however, because a thread identifier (pthread_t) is meaningful only within the process that created it.

The signal sent by pthread_kill is handled like any other signal. If the "target" thread has the signal masked, it will be marked pending against that thread. If the thread is waiting for the signal in sigwait (covered in Section 6.6.4), the thread will receive the signal. If the thread does not have the signal masked, and is not blocked in sigwait, the current signal action will be taken.

Remember that, aside from signal-catching functions, signal actions affect the process. Sending the SIGKILL signal to a specific thread using pthread_kill will kill the process, not just the specified thread. Use pthread_cancel to get rid of a particular thread (see Section 5.3). Sending SIGSTOP to a thread will stop all threads in the process until a SIGCONT is sent by some other process.

The raise function specified by ANSI C has traditionally been mapped to a kill for the current process. That is, raise(SIGABRT) is usually the same as kill(getpid (), SIGABRT).

With multiple threads, code calling raise is most likely to intend that the signal be sent to the calling thread, rather than to some arbitrary thread within the process. Pthreads specifies that raise (SIGABRT) is the same as pthread_kill(pthread_self(),SIGABRT).

The following program, susp.c, uses pthread_kill to implement a portable "suspend and resume" (or, equivalently, "suspend and continue") capability much like that provided by the Solaris "UI threads" interfaces thr_suspend and thr_continue[6]. You call the thd_suspend function with the pthread_t of a thread, and when the function returns, the specified thread has been suspended from execution. The thread cannot execute until a later call to thd_continue is made with the same pthread_t.

A request to suspend a thread that is already suspended has no effect. Calling thd_continue a single time for a suspended thread will cause it to resume execution, even if it had been suspended by multiple calls to thd_suspend. Calling thd_continue for a thread that is not currently suspended has no effect.

Suspend and resume are commonly used to solve some problems, for example, multithread garbage collectors, and may even work sometimes if the programmer is very careful. This emulation of suspend and resume may therefore be valuable to the few programmers who really need these functions. Beware, however, that should you suspend a thread while it holds some resource (such as a mutex), application deadlock can easily result.

6 The symbol ITERATIONS defines how many times the "target" threads will loop. If this value is set too small, some or all of the threads will terminate before the main thread has been able to suspend and continue them as it desires. If that happens, the program will fail with an error message — increase the value of ITERATIONS until the problem goes away.

12 The variable sentinel is used to synchronize between a signal-catching function and another thread. "Oh?" you may ask, incredulously. This mechanism is not perfect — the suspending thread (the one calling thd_suspend) waits in a loop, yielding the processor until this sentinel changes state. The volatile storage attribute ensures that the signal-catching function will write the value to memory. Remember, you cannot use a mutex within a signal-catching function.

22-40 The suspend_signal_handler function will be established as the signal-catching function for the "suspend" signal, SIGUSR1. It initializes a signal mask to block all signals except SIGUSR2, which is the "resume" signal, and then waits for that signal by calling sigsuspend. Just before suspending itself, it sets the sentinel variable to inform the suspending thread that it is no longer executing user code — for most practical purposes, it is already suspended.

The purpose for this synchronization between the signal-catching function and thd_suspend is that, to be most useful, the thread calling thd_suspend must be able to know when the target thread has been successfully suspended. Simply calling pthread_kill is not enough, because the system might not deliver the signal for a substantial period of time; we need to know when the signal has been received.

47-51 The resume_signal_handler function will be established as the signal-catching function for the "resume" signal, SIGUSR1. The function isn't important, since the signal is sent only to interrupt the call to sigsuspend in suspend_signal_handler.

? susp.c part1 signal-catching functions

1 #include <pthread.h>
2 #include <signal.h>
3 #include "errors.h"
4
5 #define THREAD_COUNT 20
6 #define ITERATIONS 40000
*A semaphore, as described later in Section 6.6.6, would provide cleaner, and somewhat safer, synchronization. The thd_suspend would call sem_wait on a semaphore with an inltial value of 0, and the signal-catching function would call sem_post to wake it.
7
8 unsigned long thread_count = THREAD_COUNT;
9 unsigned long iterations = ITERATIONS;
10 pthread_mutex_t the_mutex = PTHREAD_MUTEX_INITIALIZER;
11 pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
12 volatile int sentinel = 0;
13 pthread_once_t once = PTHREAD_ONCE_INIT;
14 pthread_t *array = NULL, null_pthread = {0};
15 int bottom = 0;
16 int inited = 0; 17
18 /*
19 * Handle SIGUSR1 in the target thread, to suspend it until
20 * receiving SIGUSR2 (resume).
21 */
22 void
23 suspend_signal_handler (int sig)
24 {
25  sigset_t signal_set;

26
27 /*
28 * Block all signals except SIGUSR2 while suspended.
29 */
30  sigfillset (&signal_set);
31  sigdelset (&signal_set, SIGUSR2);
32  sentinel = 1;
33  sigsuspend (&signal_set);
34
35 /*
36 * Once I'm here, I've been resumed, and the resume signal
37 * handler has been run to completion.
38 */
39  return;
40 }

41
42 /*
43 * Handle SIGUSR2 in the target thread, to resume it. Note that
44 * the signal handler does nothing. It exists only because we need
45 * to cause sigsuspend() to return.
46 */
47 void
48 resume_signal_handler (int sig)
49 {
50  return;
51 }

The suspend_init_routine function dynamically initializes the suspend/ resume package when the first call to thd_suspend is made. It is actually called indirectly by pthread_once.

15-16 It allocates an initial array of thread identifiers, which is used to record the identifiers of all threads that have been suspended. This array is used to ensure that multiple calls to thd_suspend have no additional effect on the target thread, and that calling thd_continue for a thread that is not suspended has no effect.

21-35 It sets up signal actions for the SIGUSR1 and SIGUSR2 signals, which will be used, respectively, to suspend and resume threads.

? susp.c part 2 initialization

1 /*
2 * Dynamically initialize the "suspend package" when first used
3 * (called by pthread_once).
4 */
5 void
6 suspend_init_routine (void)
7 {
8  int status;
9  struct sigaction sigusrl, sigusr2;
10
11 /*
12 * Allocate the suspended threads array. This array is used
13 * to guarentee idempotency
14 */
15  bottom = 10;
16  array = (pthread_t*) calloc (bottom, sizeof (pthread_t)); 17
18 /*
19 * Install the signal handlers for suspend/resume.
20 */
21  sigusrl.sa_flags = 0;
22  sigusrl.sa_handler = suspend_signal_handler; 23
24  sigemptyset (&sigusrl.sa_mask);
25  sigusr2.sa_flags = 0;
26  sigusr2.sa_handler = resume_signal_handler;
27  sigusr2.sa_mask = sigusrl.sa_mask;
28
29  status = sigaction (SIGUSR1, &sigusrl, NULL);
30  if (status == -1)
31  errno_abort ("Installing suspend handler"); 32
33  status = sigaction (SIGUSR2, &sigusr2, NULL);
34  if (status == -1)
35  errno_abort ("Installing resume handler");
36
37  inited = 1;
38  return;
39 }

9-40 The thd_suspend function suspends a thread, and returns when that thread has ceased to execute user code. It first ensures that the suspend/resume package is initialized by calling pthread_once. Under protection of a mutex, it searches for the target thread's identifier in the array of suspended thread identifiers. If the thread is already suspended, thd_suspend returns successfully.

47-60 Determine whether there is an empty entry in the array of suspended threads and, if not, realloc the array with an extra entry.

65-78 The sentinel variable is initialized to 0, to detect when the target thread suspension occurs. The thread is sent a SIGUSR1 signal by calling pthread_kill, and thd_suspend loops, calling sched_yield to avoid monopolizing a processor, until the target thread responds by setting sentinel. Finally, the suspended thread's identifier is stored in the array.

susp.c part 3 thd_suspend

1 /*
2 * Suspend a thread by sending it a signal (SIGUSR1), which will
3 * block the thread until another signal (SIGUSR2) arrives.
4 *
5 * Multiple calls to thd_suspend for a single thread have no
6 * additional effect on the thread — a single thd_continue
7 * call will cause it to resume execution.
8 */
9 int
10 thd_suspend (pthread_t target_thread)
11 {
12 int status;
13 int i = 0;
14
15 /*
16 * The first call to thd_suspend will initialize the
17 * package.
18 */
19 status = pthread_once (&once, suspend_init_routine);
20 if (status != 0)
21  return status;

22
23 /*
24 * Serialize access to suspend, makes life easier.
25 */
26 status = pthread_mutex_lock (&mut);
27 if (status != 0)
28  return status;

29
30 /*
31 * Threads that are suspended are added to the target_array;
32 * a request to suspend a thread already listed in the array
33 * is ignored. Sending a second SIGUSR1 would cause the
34 * thread to resuspend itself as soon as it is resumed.
35 */
36 while (i < bottom)
37  if (array[i++] == target_thread) {
38  status = pthread_mutex_unlock (&mut);
39  return status;
40 }

41
42 /*
43 * Ok, we really need to suspend this thread. So, let's find
44 * the location in the array that we'll use. If we run off
45 * the end, realloc the array for more space.
46 */
47 i = 0;
48 while (array[i] != 0)
49  i++;

50
51 if (i == bottom) {
52  array = (pthread_t*) realloc (
53  array, (++bottom * sizeof (pthread_t)));
54  if (array == NULL) {
55  pthread_mutex_unlock (&mut);
56  return errno;
57  }

58
59  array[bottom] = null_pthread; /* Clear new entry */
60 }

61
62 /*
63 * Clear the sentinel and signal the thread to suspend.
64 */
65 sentinel = 0;
66 status = pthread_kill (target_thread, SIGUSR1);
67 if (status != 0) {
68  pthread_mutex_unlock (&mut);
69  return status;
70 }
71
72 /*
73 * Wait for the sentinel to change.
74 */
75 while (sentinel == 0)
76  sched_yield ();
77
78 array[i] = target_thread;
79
80  status = pthread_mutex_unlock (&mut);
81  return status;
82 }

23-26 The thd_continue function first checks whether the suspend/resume package has been initialized (inited is not 0). If it has not been initialized, then no threads are suspended, and thd_continue returns with success.

33-39 If the specified thread identifier is not found in the array of suspended threads, then it is not suspended — again, return with success.

45-51 Send the resume signal, SIGUSR2. There's no need to wait — the thread will resume whenever it can, and the thread calling thd_continue doesn't need to know.

? susp.c part 4 thd_continue

1 /*
2 * Resume a suspended thread by sending it SIGUSR2 to break
3 * it out of the sigsuspend() in which it's waiting. If the
4 * target thread isn't suspended, return with success.
5 */
6 int
7 thd_continue (pthread_t target_thread)
8 {
9 int status;
10 int i = 0;

11
12 /*
13 * Serialize access to suspend, makes life easier.
14 */
15 status = pthread_mutex_lock (&mut);
16 if (status != 0)
17  return status;

18
19 /*
20 * If we haven't been initialized, then the thread must be
21 * "resumed"; it couldn't have been suspended!
22 */
23 if (!inited) {
24  status = pthread_mutex_unlock (&mut);
25  return status;
26 }

27
28 /*
29 * Make sure the thread is in the suspend array. If not, it
30 * hasn't been suspended (or it has already been resumed) and
31 * we can just carry on.
32 */
33 while (array[i] != target_thread && i < bottom)
34  i++;
35
36 if (i >= bottom) {
37  pthread_mutex_unlock (&mut);
38  return 0;
39 }

40
41 /*
42 * Signal the thread to continue, and remove the thread from
43 * the suspended array.
44 */
45 status = pthread_kill (target_thread, SIGUSR2);
46 if (status != 0) {
47  pthread_mutex_unlock (&mut);
48  return status;
49 }

50
51 array[i] = 0; /* Clear array element */
52 status = pthread_mutex_unlock (&mut);
53 return status;
54 }

2-25 The thread_routine function is the thread start routine for each of the "target" threads created by the program. It simply loops for a substantial period of time, periodically printing a status message. On each iteration, it yields to other threads to ensure that the processor time is apportioned "fairly" across all the threads.

Notice that instead of calling printf, the function formats a message with sprintf and then displays it on stdout (file descriptor 1) by calling write. This illustrates one of the problems with using suspend and resume (thd_suspend and thd_continue) for synchronization. Suspend and resume are scheduling functions, not synchronization functions, and using scheduling and synchronization controls together can have severe consequences.

Incautious use of suspend and resume can deadlock your application.

In this case, if a thread were suspended while modifying a stdio stream, all other threads that tried to modify that stdio stream might block, waiting for a mutex that is locked by the suspended thread. The write function, on the other hand, is usually a call to the kernel — the kernel is atomic with respect to signals, and therefore can't be suspended. Use of write, therefore, cannot cause a deadlock.

In general, you cannot suspend a thread that may possibly hold any resource, if that resource may be required by some other thread before the suspended thread is resumed. In particular, the result is a deadlock if the thread that would resume the suspended thread first needs to acquire the resource. This prohibition includes, especially, mutexes used by libraries you call — such as the mutexes used by malloc and free, or the mutexes used by stdio.

36-42 Threads are created with an attributes object set to create threads detached, rather than joinable. The result is that threads will cease to exist as soon as they terminate, rather than remaining until main calls pthread_join. The pthread_kill function does not necessarily fail if you attempt to send a signal to a terminated thread (the standard is silent on this point), and you may be merely setting a pending signal in a thread that will never be able to act on it. If this were to occur, the thd_suspend routine would hang waiting for the thread to respond. Although pthread_kill may not fail when sending to a terminated thread, it will fail when sending to a thread that doesn't exist — so this attribute converts a possible hang, when the program is run with ITERATIONS set too low, into an abort with an error message.

51-85 The main thread sleeps for two seconds after creating the threads to allow them to reach a "steady state." It then loops through the first half of the threads, suspending each of them. It waits an additional two seconds and then resumes each of the threads it had suspended. It waits another two seconds, suspends each of the remaining threads (the second half), and then after another two seconds resumes them.

By watching the status messages printed by the individual threads, you can see the pattern of output change as the threads are suspended and resumed.

? susp.c  part 5 sample program

1 static void *
2 thread_routine (void *arg)
3 {
4 int number = (int)arg;
5 int status;
6 int i;
7 char buffer[128] ;

8
9 for (i = 1; i <= iterations; i++) {
10 /*
11 * Every time each thread does 5000 interations, print
12 * a progress report.
13 */
14 if (i % 2000 == 0) {
15  sprintf (
16  buffer, "Thread %02d: %dn",
17  number, i);
18  write (1, buffer, strlen (buffer));
19 }

20
21 sched_yield ();
22 }

23
24 return (void *)0;
25 }

26
27 int
28 main (int argc, char *argv[])
29 {
30 pthread_t threads[THREAD_COUNT];
31 pthread_attr_t detach;
32 int status;
33 void *result;
34 int i;

35
36 status = pthread_attr_init (&detach);
37 if (status != 0)
38  err_abort (status, "Init attributes object");
39 status = pthread_attr_setdetachstate (
40  &detach, PTHREAD_CREATE_DETACHED);
41 if (status != 0)
42  err_abort (status, "Set create-detached"); 43
44 for (i = 0; i< THREAD_COUNT; i++) {
45  status = pthread_create (
46  &threads[i], &detach, thread_routine, (void *)i);
47  if (status != 0)
48  err_abort (status, "Create thread");
49 }

50
51 sleep (2);

52
53 for (i = 0; i < THREAD_COUNT/2; i++) {
54  printf ("Suspending thread %d.n", i);
55  status = thd_suspend (threads[i]);
56  if (status != 0)
57  err_abort (status, "Suspend thread");
58 }

59
60 printf ("Sleeping ...n");
61 sleep (2);

62
63 for (i = 0; i < THREAD_COUNT/2; i++) {
64  printf ("Continuing thread %d.n", i);
65  status = thd_continue (threads[i]);
66  if (status != 0)
67  err_abort (status, "Suspend thread");
68 }

69
70 for (i = THREAD_COUNT/2; i < THREAD_COUNT; i++) {
71  printf ("Suspending thread %d.n", i);
72  status = thd_suspend (threads[i]);
73  if (status != 0)
74  err_abort (status, "Suspend thread");
75 }

76
77 printf ("Sleeping ...n");
78 sleep (2);

79
80 for (i = THREAD_COUNT/2; i < THREAD_COUNT; i++) {
81  printf ("Continuing thread %d.n", i);
82  status = thd_continue (threads[i]);
83  if (status != 0)
84  err_abort (status, "Continue thread");
85  }

86
87 pthread_exit (NULL); /* Let threads finish */
88 }

? susp.c part 5 sampleprogram

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


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