Êíèãà: Programming with POSIX® Threads

6.6.6 Semaphores: synchronizing with a signal-catching function

6.6.6 Semaphores: synchronizing with a signal-catching function

#ifdef _POSIX_SEMAPHORES

int sem_init (sem_t *sem, int pshared, unsigned int value);

int sem_destroy (sem_t *sem);

int sem_wait (sem_t*sem);

int sem_trywake (sem_t *sem);

int sem_post (sem_t *sem);

int sem_getvalue (sem_t *sem, int *sval);

#endif

Although mutexes and condition variables provide an ideal solution to most synchronization needs, they cannot meet all needs. One example of this is a need to communicate between a POSIX signal-catching function and a thread waiting for some asynchronous event. In new code, it is best to use sigwait or sigwaitinfo rather than relying on a signal-catching function, and this neatly avoids this problem. However, the use of asynchronous POSIX signal-catching functions is well established and widespread, and most programmers working with threads and existing code will probably encounter this situation.

To awaken a thread from a POSIX signal-catching function, you need a mechanism that's reentrant with respect to POSIX signals (async-signal safe). POSIX provides relatively few of these functions, and none of the Pthreads functions is included. That's primarily because an async-signal safe mutex lock operation would be many times slower than one that isn't async-signal safe. Outside of the kernel, making a function async-signal safe usually requires that the function mask (block) signals while it runs — and that is expensive.

In case you're curious, here is the full list of POSIX 1003.1-1996 functions that are async-signal safe (some of these functions exist only when certain POSIX options are defined, such as _POSIX_ASYNCHRONOUS_IO or _POSIX_TIMERS):

access getoverrun sigismember
aio_error getgroups sigpending
aio_return getpgrp sigprocmask
aio_suspend getpid sigqueue
alarm getppid sigsuspend
cfgetispeed getuid sleep
cfgetospeed kill stat
cfsetispeed link sysconf
cfsetospeed lseek tcdrain
chdir mkdir tcflow
chmod mkfifo tcflush
chown open tcgetattr
clock_gettime pathconf tcgetpgrp
close pause tcsendbreak
creat pipe tcsetattr
dup2 read tcsetpgrp
dup rename time
execle rmdir timer_getoverrun
execve sem_post timer_gettime
_exit setgid timer_settime
fcntl setpgid times
fdatasync setsid umask
fork setuid uname
fstat sigaction unlink
fsync sigaddset utime
getegid sigdelset wait
geteuid sigemptyset waitpid
getgid sigfillset write

POSIX.1b provides counting semaphores, and most systems that support Pthreads also support POSIX.1b semaphores. You may notice that the sem_post function, which wakes threads waiting on a semaphore, appears in the list of async-signal safe functions. If your system supports POSIX semaphores (<unistd.h> defines the _POSIX_SEMAPHORES option), then Pthreads adds the ability to use semaphores between threads within a process. That means you can post a semaphore, from within a POSIX signal-catching function, to wake a thread in the same process or in another process.

A semaphore is a different kind of synchronization object — it is a little like a mutex, a little like a condition variable. The differences can make semaphores a little harder to use for many common tasks, but they make semaphores substantially easier to use for certain specialized purposes. In particular, semaphores can be posted (unlocked or signaled) from a POSIX signal-catching function.

Semaphores are a general synchronization mechanism. We just have no reason to use them that way.

I am emphasizing the use of semaphores to pass information from a signal-catching function, rather than for general use, for a couple of reasons. One reason is that semaphores are part of a different standard. As I said, most systems that support Pthreads will also support POSIX. lb, but there is no such requirement anywhere in the standard. So you may well find yourself without access to semaphores, and you shouldn't feel dependent on them. {Of course, you may also find yourself with semaphores and without threads — but in that case, you should be reading a different book.)

Another reason for keeping semaphores here with signals is that, although semaphores are a completely general synchronization mechanism, it can be more difficult to solve many problems using semaphores—mutexes and condition variables are simpler. If you've got Pthreads, you only need semaphores to handle this one specialized function — waking a waiting thread from a signal-catching function. Just remember that you can use them for other things when they're convenient and available.

POSIX semaphores contain a count, but no "owner," so although they can be used essentially as a lock, they can also be used to wait for events. The terminology used in the POSIX semaphore operations stresses the "wait" behavior rather than the "lock" behavior. Don't be confused by the names, though; there's no difference between "waiting" on a semaphore and "locking" the semaphore.

A thread waits on a semaphore (to lock a resource, or wait for an event) by calling sem_wait. If the semaphore counter is greater than zero, sem_wait decrements the counter and returns immediately. Otherwise, the thread blocks. A thread can post a semaphore (to unlock a resource, or awaken a waiter) by calling sem_post. If one or more threads are waiting on the semaphore, sem_post will wake one waiter (the highest priority, or earliest, waiter). If no threads are waiting, the semaphore counter is incremented.

The initial value of the semaphore counter is the distinction between a "lock" semaphore and a "wait" semaphore. By creating a semaphore with an initial count of 1, you allow one thread to complete a sem_wait operation without blocking— this "locks" the semaphore. By creating a semaphore with an initial count of 0, you force all threads that call sem_wait to block until some thread calls sem_post.

The differences in how semaphores work give the semaphore two important advantages over mutexes and condition variables that may be of use in threaded programs:

1. Unlike mutexes, semaphores have no concept of an "owner." This means that any thread may release threads blocked on a semaphore, much as if any thread could unlock a mutex that some thread had locked. (Although this is usually not a good programming model, there are times when it is handy.)

2. Unlike condition variables, semaphores can be independent of any external state. Condition variables depend on a shared predicate and a mutex for waiting—semaphores do not.

A semaphore is represented in your program by a variable of type sem_t. You should never make a copy of a sem_t variable — the result of using a copy of a sem_t variable in the sem_wait, sem_trywait, sem_post, and sem_destroy functions is undefined. For our purposes, a sem_t variable is initialized by calling the sem_init function. POSIX. lb provides other ways to create a "named" semaphore that can be shared between processes without sharing memory, but there is no need for this capability when using a semaphore within a single process.

Unlike Pthreads functions, the POSIX semaphore functions use errno to report errors. That is, success is designated by returning the value 0, and errors are designated by returning the value -1 and setting the variable errno to an error code.

If you have a section of code in which you want up to two threads to execute simultaneously while others wait, you can use a semaphore without any additional state. Initialize the semaphore to the value 2; then put a sem_wait at the beginning of the code and a sem_post at the end. Two threads can then wait on the semaphore without blocking, but a third thread will find the semaphore's counter at 0, and block. As each thread exits the region of code it posts the semaphore, releasing one waiter (if any) or restoring the counter.

The sem_getvalue function returns the current value of the semaphore counter if there are no threads waiting. If threads are waiting, sem_getvalue returns a negative number. The absolute value of that number tells how many threads are waiting on the semaphore. Keep in mind that the value it returns may already be incorrect — it can change at any time due to the action of some other thread.

The best use for sem_getvalue is as a way to wake multiple waiters, somewhat like a condition variable broadcast. Without sem_getvalue, you have no way of knowing how many threads might be blocked on a semaphore. To "broadcast" a semaphore, you could call sem_getvalue and sem_post in a loop until sem_getvalue reports that there are no more waiters.

But remember that other threads can call sem_post during this loop, and there is no synchronization between the various concurrent calls to sem_post and sem_getvalue. You can easily end up issuing one or more extra calls to sem_post, which will cause the next thread that calls sem_wait to find a value greater than 0, and return immediately without blocking.

The program below, semaphore_signal.c, uses a semaphore to awaken threads from within a POSIX signal-catching function. Notice that the sem_init call sets the initial value to 0 so that each thread calling sem_wait will block. The main program then requests an interval timer, with a POSIX signal-catching function that will wake one waiting thread by calling sem_post. Each occurrence of the POSIX timer signal will awaken one waiting thread. The program will exit when each thread has been awakened five times.

32-35 Notice the code to check for EINTR return status from the sem_wait call. The POSIX timer signal in this program will always occur while one or more threads are blocked in sem_wait. When a signal occurs for a process (such as a timer signal), the system may deliver that signal within the context of any thread within the process. Likely "victims" include threads that the kernel knows to be waiting, for example, on a semaphore. So there is a fairly good chance that the sem_wait thread will be chosen, at least sometimes. If that occurs, the call to sem_wait will return with EINTR. The thread must then retry the call. Treating an EINTR return as "success" would make it appear that two threads had been awakened by each call to sem_post: the thread that was interrupted, and the thread that was awakened by the sem_post call.

? semaphore_signal.c

1 #include <sys/types.h>
2 #include <unistd.h>
3 #include <pthread.h>
4 #include <semaphore.h>
5 #include <signal.h>
6 #include <time.h>
7 #include "errors.h"

8
9 sem_t semaphore;

10
11 /*
12 * Signal-catching function.
13 */
14 void signal_catcher (int sig)
15 {
16  if (sem_post (&semaphore) == -1)
17  errno_abort ("Post semaphore");
18 }

19
20 /*
21 * Thread start routine which waits on the semaphore.
22 */
23 void *sem_waiter (void *arg)
24 {
25 int number = (int)arg;
26 int counter;

27
28 /*
29 * Each thread waits 5 times.
30 */
31 for (counter = 1; counter <= 5; counter++) {
32 while (sem_wait (&semaphore) == -1) {
33  if (errno != EINTR)
34  errno_abort ("Wait on semaphore");
35 }
36 printf ("%d waking (%d)...n", number, counter);
37 }
38 return NULL;
39 }

40
41 int main (int argc, char *argv[])
42 {
43 int thread_count, status;
44 struct sigevent sig_event;
45 struct sigaction sig_action;
46 sigset_t sig_mask;
47 timer_t timer_id;
48 struct itimerspec timer_val;
49 pthread_t sem_waiters[5];
50
51 #if !defined(_POSIX_SEMAPHORES) || !defined(_POSIX_TIMERS)
52 #if !defined(_POSIX_SEMAPHORES)
53 printf ("This system does not support POSIX semaphoresn");
54 # endif
55 #if !defined(_POSIX_TIMERS)
56 printf ("This system does not support POSIX timersn");
57 #endif
58 return -1;
59 #else
60 sem_init (&semaphore, 0, 0);

61
62 /*
63 * Create 5 threads to wait on a semaphore.
64 */
65 for (thread_count = 0; thread_count < 5; thread_count++) {
66  status = pthread_create (
67  &sem_waiters[thread_count], NULL,
68  sem_waiter, (void*)thread_count);
69 if (status != 0)
70  err_abort (status, "Create thread");
71 }

72
73 /*
74 * Set up a repeating timer using signal number SIGRTMIN,
75 * set to occur every 2 seconds.
76 */
77 sig_event.sigev_value.sival_int = 0;
78 sig_event.sigev_signo = SIGRTMIN;
79 sig_event.sigev_notify = SIGEV_SIGNAL;
80 if (timer_create (CLOCK_REALTIME, &sig_event, &timer_id) == -1)
81  errno_abort ("Create timer");
82 sigemptyset (&sig_mask);
83 sigaddset (&sig_mask, SIGRTMIN);
84 sig_action.sa_handler = signal_catcher;
85 sig_action.sa_mask = sig mask;
86 sig_action.sa_flags = 0;
87 if (sigaction (SIGRTMIN, &sig_action, NULL) == -1)
88 errno_abort ("Set signal action");
89 timer_val.it_interval.tv_sec = 2;
90 timer_val.it_interval.tv_nsec = 0;
91 timer_val.it_value.tv_sec = 2;
92 timer_val.it_value.tv_nsec = 0;
93 if (timer_settime (timer_id, 0, &timer_val, NULL) == -1)
94 errno_abort ("Set timer");

95
96 /*
97 * Wait for all threads to complete.
98 */
99 for (thread_count = 0; thread_count < 5; thread_count++) {
100  status = pthread_join (sem_waiters[thread_count], NULL);
101  if (status != 0)
102  err_abort (status, "Join thread");
103 }
104 return 0;
105 #endif
106 }

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


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