pthread_sigmask 控制线程的信号掩码

在Linux的多线程中使用信号机制,与在进程中使用信号机制有着根本的区别,可以说是完全不同。在进程环境中,对信号的处理是,先注册信号处理函数,当信号异步发生时,调用处理函数来处理信号。它完全是异步的(我们完全不知到信号会在进程的那个执行点到来!)。然而信号处理函数的实现,有着许多的限制;比如有一些函数不能在信号处理函数中调用;再比如一些函数read、recv等调用时会被异步的信号给中断(interrupt),因此我们必须对在这些函数在调用时因为信号而中断的情况进行处理(判断函数返回时 enno 是否等于 EINTR)。
但是在多线程中处理信号的原则却完全不同,它的基本原则是:将对信号的异步处理,转换成同步处理,也就是说用一个线程专门的来“同步等待”信号的到来,而其它的线程可以完全不被该信号中断/打断(interrupt)。这样就在相当程度上简化了在多线程环境中对信号的处理。而且可以保证其它的线程不受信号的影响。这样我们对信号就可以完全预测,因为它不再是异步的,而是同步的(我们完全知道信号会在哪个线程中的哪个执行点到来而被处理!)。而同步的编程模式总是比异步的编程模式简单。其实多线程相比于多进程的其中一个优点就是:多线程可以将进程中异步的东西转换成同步的来处理。

1.sigwait 监听信号集set中所包含的信号,并将其存在signo中
sigwait()函数暂停调用线程的执行,直到信号集中指定的信号之一被传递为止。在多线程代码中,总是使用sigwait或者sigwaitinfo或者sigtimedwait等函数来处理信号。而不是signal或者sigaction等函数。因为在一个线程中调用signal或者sigaction等函数会改变所有线程中的信号处理函数。而不是仅仅改变调用signal/sigaction的那个线程的信号处理函数。
注意:调用sigwait同步等待的信号必须在调用线程中被屏蔽,并且通常应该在所有的线程中被屏蔽(这样可以保证信号绝不会被送到除了调用sigwait的任何其它线程),这是通过利用信号掩码的继承关系来达到的。

sigwait - wait for a signal
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);

2、pthread_sigmask函数:
每个线程均有自己的信号屏蔽集(信号掩码),可以使用pthread_sigmask函数来屏蔽某个线程对某些信号的 响应处理,仅留下需要处理该信号的线程来处理指定的信号。实现方式是:利用线程信号屏蔽集的继承关系(在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进程的掩码

int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
How:
SIG_BLOCK:     结果集是当前集合参数集的并集
SIG_UNBLOCK: 结果集是当前集合参数集的差集
SIG_SETMASK: 结果集是由参数集指向的集

3、pthread_kill函数:
在多线程程序中,一个线程可以使用pthread_kill对同一个进程中指定的线程(包括自己)发送信号。注意在多线程中 一般不使用kill函数发送信号,因为kill是对进程发送信号,结果是:正在运行的线程会处理该信号,如果该线程没有注册信号处理函数,那么会导致整个进程退出。

int pthread_kill(pthread_tthread, intsig);

实例1:屏蔽信号SIGINT

/* 示例一:屏蔽信号SIGINT
   编译:gcc pthread_sigmask1.c -lpthread
   运行后,你发现你按下CTRL+C(触发SIGINT信号),这个程序根本停不下来。因为SIGINT信号已经如我们所愿被屏蔽掉了。
*/
 
#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#include <string.h>
 
int main(int argc, char** argv)
{
  pthread_t tid = pthread_self();
 
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  pthread_sigmask(SIG_SETMASK, &mask, NULL);//SIG_BLOCK SIG_SETMASK 会屏蔽掉SIGINT,但SIG_UNBLOCK不会屏蔽SIGINT
 
  printf("[main][%lu] working hard ...\n", tid);
 
  sleep(60);
 
  printf("[main][%lu] good bye and good luck!\n", tid);
  return 0;
}

示例二:主进程创建出来的线程将继承主进程的掩码

/* 示例二:主进程创建出来的线程将继承主进程的掩码
   来源:http://www.leoox.com/?p=321
   编译:gcc pthread_sigmask2.c -lpthread
	运行后,你可以发现,子线程果然接收不到pthread_kill发送给自己的SIGINT信号,
	但可以收到SIGUSR1信号!本来要休眠300s,但是收到了SIGUSR1信号,
	才休眠5秒就(提前294秒)醒来say goodbye了。
运行结果:
[lgh@lghvm001 thread]$ ./a.out 
[main][139999919077120] working hard ...
>>> Thread[139999919068928] Running ......
[main][139999919077120] send signal SIGINT ...
Thread[139999919068928] catch signo = 10
Thread[139999919068928] waitup(294), and say good bye!
[main][139999919077120] good bye and good luck!
[lgh@lghvm001 thread]$ 
*/
 
 
 
#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#include <string.h>
 
void handler(int signo)
{
  pthread_t tid = pthread_self();
  printf("Thread[%lu] catch signo = %d\n", tid, signo);
  return;
}
 
void* run(void *param)
{
  pthread_t tid = pthread_self();
  printf(">>> Thread[%lu] Running ......\n", tid);
 
  int rc = sleep(300);
  printf("Thread[%lu] waitup(%d), and say good bye!\n", tid, rc);
  return NULL;
}
 
int main(int argc, char** argv)
{
  int ret = 0, i = 0;
  pthread_t tid = pthread_self();
  
  /* 注册SIGUSR1信号处理函数 */
  struct sigaction sa;
  memset(&sa, 0, sizeof(sa));
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  sa.sa_handler = handler;
  sigaction(SIGUSR1, &sa, NULL);
 
  /* 屏蔽信号SIGINT */
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  pthread_sigmask(SIG_BLOCK, &mask, NULL);
 
  pthread_t threads[2];
  pthread_create(&threads[0], NULL, run, NULL);
  printf("[main][%lu] working hard ...\n", tid);
  sleep(1);
 
  /* 主进程创建出来的线程将继承主进程的掩码。所以子线程收不到SIGINT信号。 */
  pthread_kill(threads[0], SIGINT);
  printf("[main][%lu] send signal SIGINT ...\n", tid);
  sleep(5);
 
  /* 子线程可以收到SIGUSR1信号。 */
  pthread_kill(threads[0], SIGUSR1);
 
  pthread_join(threads[0], NULL);
 
  sleep(1);
  printf("[main][%lu] good bye and good luck!\n", tid);
  return 0;
}

示例三:子线程可以后天培养自己对信号的喜好

/* 示例三:子线程可以后天培养自己对信号的喜好
   来源:http://www.leoox.com/?p=321
   编译:gcc pthread_sigmask3.c -lpthread
子线程天生继承了主线程对信号的喜好,但是自己可以通过后天的努力改变。
比如主线程喜欢SIGUSR1信号,但是子线程可以不喜欢它,屏蔽掉SIGUSR1信号。
由此可见,linux里的每个线程有自己的信号掩码,所以使用pthread_kill给指定线程发送信号时,
一定谨慎设置好线程的信号掩码。
当然,用kill发送信号,在多线程环境下,kill所产生的信号时传递到整个进程的,
并且所有线程都有机会收到这个信号,但具体是哪个线程处理这个信号,就不一定。
一般情况下,都是主线程处理这个信号。
	
运行结果:
[lgh@lghvm001 thread]$ gcc pthread_sigmask3.c -lpthread
[lgh@lghvm001 thread]$ ./a.out 
[main][140613382825728] working hard ...
>>> [1481543657]Thread[140613382817536] Running ......
[main][140613382825728] send signal SIGUSR1 ...
[1481543841]Thread[140613382825728] catch signo = 10 ...		//子线程sleep期间,kill -SIGUSR1 2839
[1481543861]Thread[140613382825728] catch signo = 10 ... done
[1481543957]Thread[140613382817536] waitup(0), and say good bye!
[main][140613382825728] good bye and good luck!
[lgh@lghvm001 thread]$
*/
#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#include <string.h>
 
void handler(int signo)
{
  pthread_t tid = pthread_self();
  printf("[%u]Thread[%lu] catch signo = %d ...\n", time(NULL), tid, signo);
  sleep(20);
  printf("[%u]Thread[%lu] catch signo = %d ... done\n", time(NULL), tid, signo);
  return;
}
 
void* run(void *param)
{
  pthread_t tid = pthread_self();
  sigset_t mask;
#if 1
  /* 这种情况下,本线程屏蔽所有的信号 */
  sigfillset(&mask);
#endif
 
#if 0
  /* 这种情况下,本线程不屏蔽任何信号 */
  sigemptyset(&mask);
#endif
 
#if 0 
  /* 这种情况,本线程屏蔽以下的指定信号 */
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGQUIT);
  sigaddset(&mask, SIGHUP);
  sigaddset(&mask, SIGTERM);
#endif 
 
  pthread_sigmask(SIG_SETMASK, &mask, NULL);
  
  printf(">>> [%u]Thread[%lu] Running ......\n", time(NULL), tid);
 
  int rc = sleep(300);
 
  printf("[%u]Thread[%lu] waitup(%d), and say good bye!\n", time(NULL), tid, rc);
  return NULL;
}
 
int main(int argc, char** argv)
{
  pthread_t tid = pthread_self();
  
  /* 注册SIGUSR1信号处理函数 */
  struct sigaction sa;
  memset(&sa, 0, sizeof(sa));
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  sa.sa_handler = handler;
  sigaction(SIGUSR1, &sa, NULL);
 
  pthread_t threads[1];
  pthread_create(&threads[0], NULL, run, NULL);
  printf("[main][%lu] working hard ...\n", tid);
  sleep(5);
 
  /* 子线程屏蔽了SIGUSR1信号,所以子线程收不到SIGUSR1信号。 */
  pthread_kill(threads[0], SIGUSR1);
  printf("[main][%lu] send signal SIGUSR1 ...\n", tid);
  sleep(5);
 
  pthread_join(threads[0], NULL);
  sleep(1);
 
  printf("[main][%lu] good bye and good luck!\n", tid);
  return 0;
}

示例四:主线程收到信号,没处理完,又来一个信号,子线程会处理吗? 会!

/* 示例四:主线程收到信号,没处理完,又来一个信号,子线程会处理吗? 会!
   来源:http://www.leoox.com/?p=321
   编译:gcc pthread_sigmask4.c -lpthread
   在一个命令行终端启动a.out,在另一个命令行终端发送4次SIGUSR1信号给a.out进程
	
运行结果:
[lgh@lghvm001 thread]$ ./a.out 
[main][139796483913472] working hard ...
>>> [1481545031]Thread[139796483905280] Running ......	//此时发送四次kill -SIGUSR1 2886,2886为a.out的进程号
[1481545054]Thread[139796483913472] catch signo = 10 ...
[1481545055]Thread[139796483905280] catch signo = 10 ...
[1481545056]Thread[139796483913472] catch signo = 10 ... done
[1481545056]Thread[139796483913472] catch signo = 10 ...
[1481545057]Thread[139796483905280] catch signo = 10 ... done
[1481545057]Thread[139796483905280] catch signo = 10 ...
[1481545058]Thread[139796483913472] catch signo = 10 ... done
[1481545059]Thread[139796483905280] catch signo = 10 ... done
>>> [1481545059]Thread[139796483905280] waitup(35), and say good bye!
[main][139796483913472] good bye and good luck!
[lgh@lghvm001 thread]$ 
*/
 
 
#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#include <string.h>
 
void handler(int signo)
{
  pthread_t tid = pthread_self();
  printf("[%u]Thread[%lu] catch signo = %d ...\n", time(NULL), tid, signo);
  /*
   * 信号处理函数休眠2秒,这期间再发送同一个信号,观察子线程的表现。
   */
  sleep(2);
  printf("[%u]Thread[%lu] catch signo = %d ... done\n", time(NULL), tid, signo);
  return;
}
 
void* run(void *param)
{
  pthread_t tid = pthread_self();
  printf(">>> [%u]Thread[%lu] Running ......\n", time(NULL), tid);
 
  int rc = sleep(60);
 
  printf(">>> [%u]Thread[%lu] waitup(%d), and say good bye!\n", time(NULL), tid, rc);
  return NULL;
}
 
int main(int argc, char** argv)
{
  pthread_t tid = pthread_self();
  
  /* 注册SIGUSR1信号处理函数 */
  struct sigaction sa;
  memset(&sa, 0, sizeof(sa));
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  sa.sa_handler = handler;
  sigaction(SIGUSR1, &sa, NULL);
 
  pthread_t threads[1];
  pthread_create(&threads[0], NULL, run, NULL);
  printf("[main][%lu] working hard ...\n", tid);
 
  pthread_join(threads[0], NULL);
  sleep(1);
 
  printf("[main][%lu] good bye and good luck!\n", tid);
  return 0;
}

如果把void handler(int signo)函数中的sleep(2)注释掉,运行结果如下:

[lgh@lghvm001 thread]$ ./a.out 
[main][140353429055232] working hard ...
>>> [1481596389]Thread[140353429047040] Running ......     //此处发送6次“kill -SIGUSR1 3123”
[1481596401]Thread[140353429055232] catch signo = 10 ...
[1481596401]Thread[140353429055232] catch signo = 10 ... done
[1481596401]Thread[140353429055232] catch signo = 10 ...
[1481596401]Thread[140353429055232] catch signo = 10 ... done
[1481596402]Thread[140353429055232] catch signo = 10 ...
[1481596402]Thread[140353429055232] catch signo = 10 ... done
[1481596402]Thread[140353429055232] catch signo = 10 ...
[1481596402]Thread[140353429055232] catch signo = 10 ... done
[1481596402]Thread[140353429055232] catch signo = 10 ...
[1481596402]Thread[140353429055232] catch signo = 10 ... done
[1481596403]Thread[140353429055232] catch signo = 10 ...
[1481596403]Thread[140353429055232] catch signo = 10 ... done
>>> [1481596449]Thread[140353429047040] waitup(0), and say good bye!
[main][140353429055232] good bye and good luck!
[lgh@lghvm001 thread]$

可见,若子线程“没空”(在sleep),主线程有空,信号都给主线程处理了。

猜你喜欢

转载自blog.csdn.net/jsh13417/article/details/83050668
今日推荐