12.8 线程和信号
在基于进程模型的信号处理已经比较吗麻烦了,引入线程后事情就更加复杂了。
每个线程拥有其自身的信号掩码,但是信号处理函数是被进程内的所有线程共享的,作为一个推论,独立的线程能够阻塞信号,但是如果一个线程修改与给定信号的处理动作的时候,所有的线程都会共享这一修改。也就是说,如果一个线程选择忽略一个给定信号,其他的线程可能会通过恢复默认呢处理或者是安装信号处理函数的方式撤销线程所做的忽略选择。
信号会只会被发送到进程内的一个线程。如果信号与硬件错误相关,那么信号通常被发送到造成时间发生的线程,其他信号,将被发送到任意一个线程。
在10.12节中,我们介绍了进程可以使用函数sigprocmask函数来阻塞信号的发送,然而,sigprocmask函数在多线程进程中的行为是未定义的,线程必须使用函数pthread_sigmask予以代替。
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
Returns: 0 if OK, error number on failure.
函数pthread_sigmask与函数sigprocmask相似,但是pthread_sigmask函数在失败的时候返回一个错误码,而函数sigprocmask在失败的时候返回-1并且设置errno变量。
参数how可以取三个不同的数值:
* SIG_BLOCK添加信号集到线程的信号掩码中;
* SIG_SETMASK使用信号集替换线程的信号掩码;
* SIG_UNBLOCK从线程的信号掩码中移除信号集;
如果oset参数不是null,那么线程的前一个信号掩码将被存储到oset指针指向的内存中。线程可以通过设置set参数为null的情况下获取其当前的信号掩码,着这种情况下,how参数将被忽略。
线程可以调用函数sigwait来等待一个或者多个信号的出现;
#include <signal.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);
Returns: 0 if OK, error number on failure.
参数set指定了线程正在等待信号的集合,参数signop指针指向的内存将会存储被发送的信号数量。
如果在sigwait函数被调用的时候,如果set中的一个信号处于挂起状态,那么函数sigwait就不会阻塞,而是直接返回,在返回之前,sigwait函数将会将从进程挂起的信号集中移除等到的信号。如果实现支持信号排队,并且一个信号的多个实例处于挂起状态,函数sigwait仅仅移除其中一个实例,其他实例仍然处于排队状态。
为了避免错误的行为,线程在调用函数sigwait之前必须先阻塞这些等待的信号。函数sigwait将会自动解除对应信号的阻塞,并且一直等待知道其中一个信号被发送,在返回之前,sigwait将会恢复线程的信号掩码,如果函数sigwait被调用的时候,对应的信号没有被阻塞,那么在完成函数sigwait之前会出现一个多于的时间窗口,在这个时间窗口内,信号可能被发送到了线程。
使用函数sigwait的一个好处就是可以简化信号处理函数的设计,因为它允许我们将异步信号处理当作是同步信号来看待,我们可以通过将指定信号添加到线程的信号掩码中,从而防止信号中断线程的执行,然后专门指定一个线程来处理信号,这些指定的线程在调用函数的时候不需要再考虑那些函数是信号处理安全的,因为在信号处理函数被调用的时候,线程处于正常的线程环境,而不是像传统情况那样:信号处理函数能够中断线程的正常执行。
如果多个线程由于调用了函数sigwait而阻塞在了同一个信号上,那么当信号被发送的时候,仅仅只有一个线程将会返回,如果一个信号被捕获,并且存在一个线程调用函数sigwait等待这一信号,如何发送信号是交给实现了决定的,实现可以允许sigwait返回或者是调用信号处理函数,但是不能同时执行这两个动作。
为了发送一个信号给进程,我们可以调用函数kill,为了发送一个信号给一个线程,我们可以调用函数pthread_kill;
#include <signal.h>
int pthread_kill(pthread_t thread, int signl);
Returns: 0 if ok, error number on failure.
我们可以传递给参数signo数值0,用于检测指定线程是否存在。如果一个信号的默认处理方式是终止进程,那么向一个线程发送这个信号也会终止掉整个进程。
注意alarm timers是进程资源,所有的线程都会共享相同的alarms集合,因此,对于一个进程中的多个线程,不可能做到使用alarm timers而不发生冲突。
Example
在图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);
/*SIGQIUT has been caught and is now blocked; do whatever */
quitflag = 0;
/*reset signal mask which unblocks ISGQUIT */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
exit(0);
}
图12.16 同步信号处理
信号处理函数不再会中断主线程,我们使用一个专门的线程来处理信号,我们可以在互斥锁的保护下修改quitflag,so that the main thread of control can’t miss the wake-up call made when we call pthread_cond_signal.我们在主线程中使用同一互斥锁来检查标记的值,并在我们等待条件的时候自动释放锁。
注意同时阻塞了信号SIGINT以及SIGQUIT信号,当我们创建线程来处理信号的时候,线程继承了当前的信号掩码,因为sigwait并不会阻塞信号,因此只有一个线程可以接受信号,这样可以使得我们在编写主线程的时候不用考虑这些中断信号的干扰。
程序运行情况如下入所示:
yuekunhu@debian:~/APUE/chapter12$ ./12_16.exe
^C
interrupt
^C
interrupt
^C
interrupt
^\yuekunhu@debian:~/APUE/chapter12$
转载于:https://www.cnblogs.com/U201013687/p/5635947.html
12.8 线程和信号相关推荐
- UNIX高级环境编程 第11、12章 线程同步及属性
第11.12章 线程及其控制 主要内容 互斥量 非递归互斥量 递归互斥量 读写锁 条件变量 自旋锁 屏障 互斥量 int pthread_mutex_init(pthread_mutex_t *res ...
- Qt线程间信号槽传递自定义数据类型(qRegisterMetaType的使用)
Qt线程间信号槽传递自定义数据类型(qRegisterMetaType的使用) #include <QMetaType> CFileDataModel::CFileDataModel(QO ...
- Linux内核信号杀死内核线程,linux内核线程对信号的处理过程.
linux中的线程分为用户线程和内核线程,用户线程是规范的线程,全面的自主性,全面的抢占性:然而内核线程就不那么好了,某种含义上未曾用户线程那么安逸,这个怎么会意呢?用户线程的编写者只必需告终利用逻辑 ...
- PySide2多线程问题示例:创建新线程、子线程发射信号到主界面
PySide2多线程问题示例:创建新线程.子线程发射信号到主界面 本文是在pyside学习过程中的记录,从无子线程.子线程在主程序中直接操作Qt界面.子线程发射信号操作主界面三个步骤出发,记录对多线程 ...
- linux线程关闭信号,Linux/UNIX用同步方法处理异步信号
一. 前言 Linux/UNIX进程信号处理复杂易出错,而用在多线程中就更加复杂脆弱,这里不探讨相关历史渊源,只给出一种在实践中简单可靠的信号处理方式.后文讨论的线程模型是POSIX thread(p ...
- 操作系统实验报告12:线程2
操作系统实验报告12 实验内容 实验内容:线程(2). 编译运行课件 Lecture14 例程代码: Algorithms 14-1 ~ 14-7. 比较 pthread 和 clone() 线程实现 ...
- 12.QT线程的两种启动方式
一.QT中的线程 QT中的线程主要是通过QThread进行管理,一个QThread对象管理程序中的一个线程. QThreads管理的线程在run()中开始执行. 默认情况下,run()通过调用exec ...
- CLR via C# 读书笔记 1-2 创建线程的成本
在clr中创建线程的代价还是比较高的 ,他需要两个部分 内存: 线程核心对象, 存放描述线程的一些内容和上下文 . (内存消耗:700B-2500B) 线程环境,存放例如异常处理链之类. (内存消耗 ...
- Linux系统编程----12(线程概念,Linux线程实现原理,栈中ebp指针和ebp指针,线程的优缺点和共享资源)
线程概念 什么是线程 在一个程序里的一个执行路线就叫做线程(thread). 更准确的定义是:线程是"一个进程内部的控制序列" 一切进程至少都有一个执行线程 线程在进程内部运行,本 ...
最新文章
- tar从压缩包里解压出指定文件
- Remoting Practice Sample
- golang 追加内容到文件
- java中char类型_【考点】JAVA中的char类型
- 使用Xcode 4编译器设定Release/Debug教程
- css div里引用em字体会变斜体_前端开发中7种必要了解的CSS长度单位
- zeros什么意思_ma=zeros(n);是什么意思'
- html设置数字显示位数,数字万用表的显示位数和精度
- 趋势探讨:容器会取代虚拟机吗?
- JavaScript数值类型及变量
- HDOJ--1262--寻找素数对
- 开发类似vs的界面_C#会不会成为移动开发最便捷的语言?
- 科研神器----数据提取软件WebPlotDigitizer的使用
- 松江区企业技术中心认定条件及奖励政策解读
- 【nn.Parameter】Pytorch特征融合自适应权重设置(可学习权重使用)
- Spring 定时器时间设置规则
- 常见的各种字幕内容总结(字幕基础、字幕介绍、字幕种类及常见格式、SRT+ASS+LYC等等)
- arcgis直方图导出地图_利用Arcgis地图工具自动输出报告地图图纸
- android app防被杀策略
- P型MOS管常用型号表,电子工程师选型必备!
热门文章
- php数字取反,[转+自]关于PHP7的新特性(涉及取反和disabled_functions绕过)
- php reflectionmethod,PHP ReflectionMethod getClosure()用法及代码示例
- MongoDB学习3——mongoDB的一些基本使用
- 825. 适龄的朋友
- 有关进程的一些基本概念
- c语言大乐透编译,Excel大乐透摇号vba代码分享,说不定就中百万了呢
- java 方法 示例_带有示例的Java EnumSetSupplementOf()方法
- Python程序可打印今天的年,月和日
- Java InputStreamReader getEncoding()方法及示例
- 0-1背包问题(物品不可分割)