2.2 sigwait
  线程可通过调用sigwait()函数等待一个或多个信号发生。
  #include <signal.h>
  int sigwait(const sigset_t *restrict sigset, int *restrict signop);
  参数sigset指定线程等待的信号集,signop指向的整数表明接收到的信号值。该函数将调用线程挂起,直到信号集中的任何一个信号被递送。该函数接收递送的信号后,将其从未决队列中移除(以防返回时信号被signal/sigaction安装的处理函数捕获),然后唤醒线程并返回。该函数执行成功时返回0,并将接收到的信号值存入signop所指向的内存空间;失败时返回错误编号(errno)。失败原因通常为EINVAL(指定信号无效或不支持),但并不返回EINTR错误。
  给定线程的未决信号集是整个进程未决信号集与该线程未决信号集的并集。若等待信号集中某个信号在sigwait()调用时处于未决状态,则该函数将无阻塞地返回。若同时有多个等待中的信号处于未决状态,则对这些信号的选择规则和顺序未定义。在返回之前,sigwait()将从进程中原子性地移除所选定的未决信号。
  若已阻塞等待信号集中的信号,则sigwait()会自动解除信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait()将恢复线程的信号屏蔽字。因此,sigwait()并不改变信号的阻塞状态。可见,sigwait()的这种“解阻-等待-阻塞”特性,与条件变量非常相似。
  为避免错误发生,调用sigwait()前必须阻塞那些它正在等待的信号。在单线程环境中,调用程序首先调用sigprocmask()阻塞等待信号集中的信号,以防这些信号在连续的sigwait()调用之间进入未决状态,从而触发默认动作或信号处理函数。在多线程程序中,所有线程(包括调用线程)都必须阻塞等待信号集中的信号,否则信号可能被递送到调用线程之外的其他线程。建议在创建线程前调用pthread_sigmask()阻塞这些信号(新线程继承信号屏蔽字),然后绝不显式解除阻塞(sigwait会自动解除信号集的阻塞状态)。
  若多个线程调用sigwait()等待同一信号,只有一个(但不确定哪个)线程可从sigwait()中返回。若信号被捕获(通过sigaction安装信号处理函数),且线程正在sigwait()调用中等待同一信号,则由系统实现来决定以何种方式递送信号。操作系统实现可让sigwait返回(通常优先级较高),也可激活信号处理程序,但不可能出现两者皆可的情况。
  注意,sigwait()与sigwaitinfo()函数功能类似。两者的区别在于,sigwait()成功时返回0并传回信号值,且失败时返回errno;而sigwaitinfo()成功时返回信号值并传回siginfo_t结构(信息更多),且失败时设置errno并返回-1。此外, 当产生等待信号集以外的信号时,该信号的处理函数可中断sigwaitinfo(),此时errno被设置为EINTR。

  对SIGKILL (杀死进程)和 SIGSTOP(暂停进程)信号的等待将被系统忽略。
  使用sigwait()可简化多线程环境中的信号处理,允许在指定线程中以同步方式等待并处理异步产生的信号。为了防止信号中断线程,可将信号加到每个线程的信号屏蔽字中,然后安排专用线程作信号处理。该专用线程可进行任何函数调用,而不必考虑函数的可重入性和异步信号安全性,因为这些函数调用来自正常的线程环境,能够知道在何处被中断并继续执行。这样,信号到来时不会打断其他线程的工作。
  这种采用专用线程同步处理信号的模型如下图所示:

  其设计步骤如下:
  1) 主线程设置信号屏蔽字,阻塞希望同步处理的信号;
  2) 主线程创建一个信号处理线程,该线程将希望同步处理的信号集作为 sigwait()的参数;
  3) 主线程创建若干工作线程。
  主线程的信号屏蔽字会被其创建的新线程继承,故工作线程将不会收到信号。
  注意,因程序逻辑需要而产生的信号(如SIGUSR1/ SIGUSR2和实时信号),被处理后程序继续正常运行,可考虑使用sigwait同步模型规避信号处理函数执行上下文不确定性带来的潜在风险。而对于硬件致命错误等导致程序运行终止的信号(如SIGSEGV),必须按照传统的异步方式使用 signal()或sigaction()注册信号处理函数进行非阻塞处理,以提高响应的实时性。在应用程序中,可根据所处理信号的不同而同时使用这两种信号处理模型。
  因为sigwait()以阻塞方式同步处理信号,为避免信号处理滞后或非实时信号丢失的情况,处理每个信号的代码应尽量简洁快速,避免调用会产生阻塞的库函数。
  2.3 pthread_kill
  应用程序可调用pthread_kill(),将信号发送给同一进程内指定的线程(包括自己)。
  #include <signal.h>
  int pthread_kill(pthread_t thread, int signo);
  该函数将signo信号异步发送至调用者所在进程内的thread线程。该函数执行成功时返回0,否则返回错误编号(errno),且不发送信号。失败原因包括ESRCH(指定线程不存在)和EINVAL(指定信号无效或不支持),但绝不返回EINTR错误。
  若signo信号取值为0(空信号),则pthread_kill()仍执行错误检查并返回ESRCH,但不发送信号。因此,可利用这种特性来判断指定线程是否存在。类似地,kill(pid, 0)可用来判断指定进程是否存在(返回-1并设置errno为ESRCH)。例如:

 

1 int ThreadKill(pthread_t tThrdId, int dwSigNo)
2 {
3     int dwRet = pthread_kill(tThrdId, dwSigNo);
4     if(dwRet == ESRCH)
5         printf("Thread %x is non-existent(Never Created or Already Quit)! ",
6               (unsigned int)tThrdId);
7     else if(dwRet == EINVAL)
8         printf("Signal %d is invalid! ", dwSigNo);
9     else
10         printf("Thread %x is alive! ", (unsigned int)tThrdId);
11
12     return dwRet;
13 }

  但应注意,系统在经过一段时间后会重新使用进程号,故当前拥有指定进程号的进程可能并非期望的进程。此外,进程存在性的测试并非原子操作。kill()向调用者返回测试结果时,被测试进程可能已终止。