即使是在基于进程的编程范型中,信号的处理有时候也是很复杂的。把线程引入编程范型,就使信号的处理变得更加复杂。

每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。这意味着单个线程可以阻止某些信号,但当某个线程修改了与某个给定信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变。这样,如果一个线程选择忽略某个给定信号,那么另一个线程就可以通过以下两种方式撤消上述线程的信号选择:恢复信号的默认处理行为,或者为信号设置一个新的信号处理程序。

进程中的信号是递送到单个线程的。如果一个信号与硬件故障相关,那么该信号一般会被发送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程。

10.12 节讨论了进程如何使用 sigprocmask 函数来阻止信号发送。然而,sigprocmask的行为在多线程的进程中并没有定义,线程必须使用pthread_sigmask

#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
//返回值:若成功,返回0;否则,返回错误编号

pthread_sigmask函数与sigprocmask函数基本相同,不过pthread_sigmask工作在线程中,而且失败时返回错误码,不再像sigprocmask中那样设置errno并返回−1set参数包含线程用于修改信号屏蔽字的信号集。

how参数可以取下列3个值之一:

  • SIG_BLOCK,把信号集添加到线程信号屏蔽字中
  • SIG_SETMASK,用信号集替换线程的信号屏蔽字;
  • SIG_UNBLOCK,从线程信号屏蔽字中移除信号集。

如果oset参数不为空,线程之前的信号屏蔽字就存储在它指向的sigset_t结构中。线程可以通过把set参数设置为NULL,并把oset参数设置为sigset_t结构的地址,来获取当前的信号屏蔽字。这种情况中的how参数会被忽略。

线程可以通过调用sigwait等待一个或多个信号的出现。

#include <signal.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);
//返回值:若成功,返回0;否则,返回错误编号

set参数指定了线程等待的信号集。返回时,signop指向的整数将包含发送信号的数量。

如果信号集中的某个信号在sigwait调用的时候处于挂起状态,那么sigwait将无阻塞地返回。在返回之前,sigwait将从进程中移除那些处于挂起等待状态的信号。如果具体实现支持排队信号,并且信号的多个实例被挂起,那么sigwait将会移除该信号的一个实例,其他的实例还要继续排队。

为了避免错误行为发生,线程在调用 sigwait 之前,必须阻塞那些它正在等待的信号。sigwait函数会原子地取消信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait将恢复线程的信号屏蔽字。如果信号在sigwait 被调用的时候没有被阻塞,那么在线程完成对sigwait的调用之前会出现一个时间窗,在这个时间窗中,信号就可以被发送给线程。

使用sigwait的好处在于它可以简化信号处理,允许把异步产生的信号用同步的方式处理。为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中。然后可以安排专用线程处理信号。这些专用线程可以进行函数调用,不需要担心在信号处理程序中调用哪些函数是安全的,因为这些函数调用来自正常的线程上下文,而非会中断线程正常执行的传统信号处理程序。

如果多个线程在 sigwait 的调用中因等待同一个信号而阻塞,那么在信号递送的时候,就只有一个线程可以从 sigwait 中返回。如果一个信号被捕获(例如进程通过使用 sigaction建立了一个信号处理程序),而且一个线程正在sigwait调用中等待同一信号,那么这时将由操作系统实现来决定以何种方式递送信号。操作系统实现可以让 sigwait 返回,也可以激活信号处理程序,但这两种情况不会同时发生。

要把信号发送给进程,可以调用kill(见10.9节)。要把信号发送给线程,可以调用pthread_kill

#include <signal.h>
int pthread_kill(pthread_t thread, int signo);
//返回值:若成功,返回0;否则,返回错误编号

可以传一个0值的signo来检查线程是否存在。如果信号的默认处理动作是终止该进程,那么把信号传递给某个线程仍然会杀死整个进程。

注意,闹钟定时器是进程资源,并且所有的线程共享相同的闹钟。所以,进程中的多个线程不可能互不干扰(或互不合作)地使用闹钟定时器(这是习题12.6的内容)。

实例

回忆图10-23所示的程序,我们等待信号处理程序设置标志表明主程序应该退出。唯一可运行的控制线程就是主线程和信号处理程序,所以阻塞信号足以避免错失标志修改。在线程中,我们需要使用互斥量来保护标志,如图12-16中的程序所示。

#include "apue.h"
#include <pthread.h>

int            quitflag;    /* set nonzero by thread */
sigset_t    mask;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t waitloc = PTHREAD_COND_INITIALIZER;

void *
thr_fn(void *arg)
{
    int err, signo;

    for (;;) {
        err = sigwait(&mask, &signo);
        if (err != 0)
            err_exit(err, "sigwait failed");
        switch (signo) {
        case SIGINT:
            printf("\ninterrupt\n");
            break;

        case SIGQUIT:
            pthread_mutex_lock(&lock);
            quitflag = 1;
            pthread_mutex_unlock(&lock);
            pthread_cond_signal(&waitloc);
            return(0);

        default:
            printf("unexpected signal %d\n", signo);
            exit(1);
        }
    }
}

int
main(void)
{
    int            err;
    sigset_t    oldmask;
    pthread_t    tid;

    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);
    if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
        err_exit(err, "SIG_BLOCK error");

    err = pthread_create(&tid, NULL, thr_fn, 0);
    if (err != 0)
        err_exit(err, "can't create thread");

    pthread_mutex_lock(&lock);
    while (quitflag == 0)
        pthread_cond_wait(&waitloc, &lock);
    pthread_mutex_unlock(&lock);

    /* SIGQUIT has been caught and is now blocked; do whatever */
    quitflag = 0;

    /* reset signal mask which unblocks SIGQUIT */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");
    exit(0);
}

图12-16 同步信号处理

我们不用依赖信号处理程序中断主控线程,有专门的独立控制线程进行信号处理。在互斥量的保护下改动quitflag的值,这样主控线程不会在调用pthread_cond_signal时错失唤醒调用。在主控线程中使用相同的互斥量来检查标志的值,并且原子地释放互斥量,等待条件的发生。

注意,在主线程开始时阻塞 SIGINTSIGQUIT。当创建线程进行信号处理时,新建线程继承了现有的信号屏蔽字。因为sigwait会解除信号的阻塞状态,所有只有一个线程可以用于信号的接收。这可以使我们对主线程进行编码时不必担心来自这些信号的中断。
运行这个程序可以得到与图10-23类似的输出结果:

$ ./a.out
^?         输入中断字符
^?         再次输入中断字符
interrupt
interrupt
^?         再次输入中断字符
^\$        现在用退出符终止
interrupt

results matching ""

    No results matching ""