zoukankan      html  css  js  c++  java
  • 线程控制之线程和信号

    每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变。

    进程中的信号是递送到单个线程的。如果信号与硬件故障或计时器超时相关,该信号就被发送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程。

    http://www.cnblogs.com/nufangrensheng/p/3515257.html中讨论了进程如何使用sigprocmask来阻止信号发送。sigpromask的行为在多线程的进程中并没有定义,线程必须使用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并返回-1。

    线程可以通过调用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(见http://www.cnblogs.com/nufangrensheng/p/3514817.html);要把信号发送到线程,可以调用pthread_kill。

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

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

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

    实例

    回忆程序清单10-16(http://www.cnblogs.com/nufangrensheng/p/3516427.html),等待信号处理程序设置标志,从而表明主程序应该退出。唯一可运行的控制线程就是主线程和信号处理程序,所以阻塞信号足以避免错失标志修改。在线程中,需要使用互斥量来保护标志,如程序清单12-6所示。

    程序清单12-6 同步信号处理

    #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    wait = 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("
    interrupt
    ");
                    break;
            
                case SIGQUIT:
                    pthread_mutex_lock(&lock);
                    quitflag = 1;
                    pthread_mutex_unlock(&lock);
                    pthread_cond_signal(&wait);
                    return(0);
    
                default:
                    printf("unexpected signal %d
    ", 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(&wait, &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);
    }

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

    注意在主线程开始时阻塞SIGINT和SIGQUIT。当创建线程进行信号处理时,新建线程继承了现有的信号屏蔽字。因为sigwait会解除信号的阻塞状态,所以只有一个线程可以用于信号的接收。这使得对主线程进行编码时不必担心来自这些信号的中断。

    运行这个程序可以得到与程序10-16类似的输出结果:

    interrupt        键入中断字符
    interrupt        再次键入中断字符
    interrupt        再一次
                        用结束字符终止

    本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

  • 相关阅读:
    vmware 虚拟机共享 windows 目录
    下载 golang.org/x 包出错不用代理的解决办法
    c++小游戏——彩票
    恶意代码要注意
    Pascal到c++,求大佬翻译!
    【CYH-01】小奔的国庆练习赛:赛后标程
    鸽巢原理及其扩展——Ramsey定理
    区块链,你知道多少?
    go语言
    10.2:异或树经验
  • 原文地址:https://www.cnblogs.com/nufangrensheng/p/3540453.html
Copyright © 2011-2022 走看看