Linux系统编程系列之线程的信号处理

news2025/1/11 19:45:44

一、为什么要有线程的信号处理

        由于多线程程序中线程的执行状态是并发的,因此当一个进程收到一个信号时,那么究竟由进程中的哪条线程响应这个信号就是不确定的,只能取决于哪条线程刚好在信号达到的瞬间被调度,这种不确定性在程序逻辑中一般是不能接受的。

二、解决办法

        1、在多线程进程中选定某条线程去响应信号

        2、其余线程对该信号进行屏蔽

三、相关函数API接口

        1、发送信号给指定线程

        

// 在进程内部,只允许在线程之间进行发送
int pthread_kill(pthread_t thread, int sig);

// 接口说明
        返回值:成功返回0,失败返回错误码
        参数thread:接收信号的线程号
        参数sig:待发送的信号


// 在进程之间进行的信号发送
int kill(pid_t pid, int sig);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数pid:接受信号的进程号
        参数sig:待发送的信号

         2、发送带参数的信号给指定线程

// 发送带参数的信号给指定线程
// 线程间
int pthread_sigqueue(pthread_t thread, 
                     int sig,
                     const union sigval value);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数thread:待接收信号的线程号
        参数sig:待发送的信号
        参数value:额外携带的参数


// 进程间
 int sigqueue(pid_t pid, int sig, const union sigval value);

// 接口说明
        返回值:成功返回0,失败返回-1
        参数pid:待接收信号的进程号
        参数sig:待发送的信号
        参数value:额外携带的参数

         3、屏蔽指定信号 

// 屏蔽指定信号
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

// 接口说明
        返回值:成功返回0,失败返回-1
        
// 参数解析:
1、how:操作命令字,比如阻塞、解除阻塞等
    SIG_BLOCK:阻塞set中的信号(原有正在阻塞的信号保持阻塞)
    SIG_SETMASK:阻塞set中的信号(原有正在阻塞的信号自动解除)
    SIG_UNBLOCK:解除set中的信号
 
2、set:当前要操作的信号集
3、oldset:若为非空,则将原有阻塞信号集保留到该oldset中
注意:该函数的操作参数不是单个信号,而是信号集。

// 信号集操作函数组
int sigemptypset(sigset_t *set);    // 清空信号集set
int sigfillset(sigset_t *set);    // 将所有信号加入信号集set中
int sigaddset(sigset_t *set, int signum); // 将信号signum添加到信号集set中
int sigdelset(sigset_t *set, int signum); // 将信号signum从信号集set中剔除
int sigsimember(const sigset_t *set, int signum); // 测试信号signum是否在信号集set中

四、案例

        1、使用线程结合信号的方式完成数据的接收和发送,要求一条线程发送数据同时发送信号指定某条线程接收数据,另外有多余线程做伪任务。

// 多线程信号处理的案例

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

char data[100];
pthread_t tid1, tid2, tid3;

// 信号响应函数
void recv_handler(int sig)
{
    printf("\nmy tid is %ld\n", pthread_self());
    printf("read data: %s\b", data);
    memset(data, 0, sizeof(data));
}


// 线程1的例程函数
void *routine1(void *arg)
{
    printf("I am recv_routine, my tid = %ld\n", tid1);

    // 设置线程分离 
    pthread_detach(pthread_self()); 

    while(1)
    {
        printf("please input data:\n");
        fgets(data, sizeof(data), stdin);

        pthread_kill(tid2, 34);  // 给线程2发送信号
        printf("send data success\n");
    }
}

// 线程2的例程函数,用来接收数据
void *routine2(void *arg)
{
    // 注册信号响应函数
    signal(34, recv_handler);

    printf("I am routine2, my tid = %ld\n", tid2);

    // 设置线程分离 
    pthread_detach(pthread_self());
    while(1)
    {
        pause();
    }
}

// 线程3的例程函数
void *routine3(void *arg)
{
    printf("I am routine3, my tid = %ld\n", tid3);

    // 设置线程分离 
    pthread_detach(pthread_self());
    while(1)
    {
        pause();
    }
}

int main(int argc, char *argv[])
{
    // 创建线程1,用来发送和接收数据
    errno = pthread_create(&tid1, NULL, routine1, NULL);
    if(errno == 0)
    {
        printf("pthread create routine1 success, tid = %ld\n", tid1);
    }
    else
    {
        perror("pthread create routine1 fail\n");
    }

   
    // 创建线程2,用来做多余线程
    errno = pthread_create(&tid2, NULL, routine2, NULL);
    if(errno == 0)
    {
        printf("pthread create routine2 success, tid = %ld\n", tid2);
    }
    else
    {
        perror("pthread create routine2 fail\n");
    }

     // 创建线程3,用来做多余线程
    errno = pthread_create(&tid3, NULL, routine3, NULL);
    if(errno == 0)
    {
        printf("pthread create routine3 success, tid = %ld\n", tid3);
    }
    else
    {
        perror("pthread create routine3 fail\n");
    }

    // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出
    // 或者加上while(1)等让主函数不退出
    pthread_exit(0);
    
    return 0;
}

          2、使用线程结合信号的方式完成数据的接收和发送,要求一条线程完成数据的发送和接收,另外两个线程屏蔽信号,做伪任务。

 

// 多线程信号处理的案例

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

char data[100];
sigset_t sigs_set; // 信号集
pid_t pid;
pthread_t tid1, tid2, tid3;

// 信号响应函数
void recv_handler(int sig)
{
    printf("\nmy tid is %ld\n", pthread_self());
    printf("read data: %s\b", data);
    memset(data, 0, sizeof(data));
}


// 线程1的例程函数
void *routine1(void *arg)
{
    printf("I am routine1, my tid = %ld\n", tid1);

    // 设置线程分离 
    pthread_detach(pthread_self()); 

    while(1)
    {
        printf("please input data:\n");
        fgets(data, sizeof(data), stdin);
        printf("send data success\n");

        kill(pid, 34);  // 给进程(所有线程)发送信号
    }
}

// 线程2的例程函数,用来接收数据
void *routine2(void *arg)
{
    printf("I am routine2, my tid = %ld\n", tid2);

    // 屏蔽(阻塞)信号集中的信号
    sigprocmask(SIG_BLOCK, &sigs_set, NULL);

    // 设置线程分离 
    pthread_detach(pthread_self());
    while(1)
    {
        pause();
    }
}

// 线程3的例程函数
void *routine3(void *arg)
{
    printf("I am routine3, my tid = %ld\n", tid3);

    // 设置线程分离 
    pthread_detach(pthread_self());

    // 屏蔽(阻塞)信号集中的信号
    sigprocmask(SIG_BLOCK, &sigs_set, NULL);

    while(1)
    {
        pause();
    }
}

int main(int argc, char *argv[])
{
    // 注册信号响应函数
    signal(34, recv_handler);

    sigemptyset(&sigs_set); // 清空信号集
    sigaddset(&sigs_set, 34);   // 把34信号加到信号集中

    pid = getpid(); // 获取进程号

    // 创建线程1,用来发送和接收数据
    errno = pthread_create(&tid1, NULL, routine1, NULL);
    if(errno == 0)
    {
        printf("pthread create routine1 success, tid = %ld\n", tid1);
    }
    else
    {
        perror("pthread create routine1 fail\n");
    }

    // 创建线程2,用来做多余线程
    errno = pthread_create(&tid2, NULL, routine2, NULL);
    if(errno == 0)
    {
        printf("pthread create routine2 success, tid = %ld\n", tid2);
    }
    else
    {
        perror("pthread create routine2 fail\n");
    }

     // 创建线程3,用来做多余线程
    errno = pthread_create(&tid3, NULL, routine3, NULL);
    if(errno == 0)
    {
        printf("pthread create routine3 success, tid = %ld\n", tid3);
    }
    else
    {
        perror("pthread create routine3 fail\n");
    }

    // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出
    // 或者加上while(1)等让主函数不退出
    pthread_exit(0);
    
    return 0;
}

五、总结

        多线程进程中的信号处理可以采用选定某一条线程来接收信号,其余线程屏蔽该信号的做法,可以结合案例加深对多线程中信号的处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1058587.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java学生成绩管理信息系统

一、 引言 学生成绩管理信息系统是一个基于Java Swing的桌面应用程序&#xff0c;旨在方便学校、老师和学生对学生成绩进行管理和查询。本文档将提供系统的详细说明&#xff0c;包括系统特性、使用方法和技术实现。 二、 系统特性 2.1 学生管理 添加学生信息&#xff1a;录…

基于SSM农产品商城系统

基于SSM农产品商城系统的设计与实现&#xff0c;前后端分离&#xff0c;文档 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 农产品列表 产品详情 个人中心 登陆界面 管…

gici-open示例数据运行(1.1开阔环境数据运行)

1、配置数据和处理模式 下载对应的数据集后&#xff0c;首先处理1.1中的开阔环境下数据&#xff0c;将option目录下的配置文件复制到1.1数据目录下&#xff08;若采用ROS编译&#xff0c;则配置文件目录为ros_wrapper/src/gici/option/ros real time estimation xxx.yaml&…

Fiddle日常运用手册(2)-使用过滤器进行接口精准拦截

关于Fiddle的基础界面大家已经了解&#xff0c;日常工作中可以进行简单的抓包和数据分析了。 但是&#xff0c;工作中我们又会发现&#xff0c;单纯的进行批量抓包会抓取很多无效的心跳接口数据导致让我们漏掉一些重要信息。那么如果我们想精准的拦截某一个IP的接口交互数据&am…

231003-四步MacOS-iPadOS设置无线竖屏随航SideCar

Step 0&#xff1a;MacOS到iPad无线竖屏随航显示&#xff0c;最终效果 Step 1&#xff1a; 下载 Better Display Step 2&#xff1a;在设置中新建虚拟屏幕&#xff0c;创建虚拟屏幕 Step 3&#xff1a;进行如下设置 Step 4&#xff1a;注意事项 ⚠️ 设置后的虚拟屏幕与Sideca…

基于SSM的餐厅点菜管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

找不到VCRUNTIME140_1.dll怎么办,VCRUNTIME140_1.dll丢失的5个解决方法

在当今的数字时代&#xff0c;我们的生活和工作都离不开电脑。然而&#xff0c;随着科技的发展&#xff0c;我们也会遇到各种各样的问题。其中&#xff0c;VCRUNTIME140_1.dll丢失的问题是许多人都会遇到的困扰。这个问题可能会导致许多应用程序无法正常运行&#xff0c;给我们…

如何在 Google Earth 中创建轨迹、路线并制作动画

如何创建航迹 https://kurviger.de/en Google 地球飞行教程(天桥动画) 选择合适的点 &#xff08;可调整视图快照&#xff09;点击录制&#xff0c;依次点击图标即可

电子计算机核心发展(继电器-真空管-晶体管)

目录 继电器 最大的机电计算机之一——哈弗Mark1号&#xff0c;IBM1944年 背景 组成 性能 核心——继电器 简介 缺点 速度 齿轮磨损 Bug的由来 真空管诞生 组成 控制开关电流 继电器对比 磨损 速度 缺点 影响 代表 第一个可编程计算机 第一个真正通用&am…

Go 代码中的文档和注释

撰写清晰、简洁和全面的代码文档的指南 在软件开发领域&#xff0c;编写代码只占了一半的战斗。另一半则围绕着创建清晰、简洁和全面的文档展开&#xff0c;这些文档不仅有助于开发人员理解代码库&#xff0c;还充当未来开发的路线图。在本指南中&#xff0c;我们将深入探讨编…

蓝桥杯每日一题2023.10.3

杨辉三角形 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 40分写法&#xff1a; 可以自己手动构造一个杨辉三角&#xff0c;然后进行循环&#xff0c;用cnt记录下循环数的个数&#xff0c;看哪个数与要找的数一样&#xff0c;输出cnt #include<bits/stdc.h> using na…

协议栈——收发数据(拼接网络包,自动重发,滑动窗口机制)

目录 协议栈何时发送数据&#xff5e; 数据长度 IP模块的分片功能 发送频率 网络包序号&#xff5e;利用syn拼接网络包ack确认网络包完整 确定偏移量 服务器ack确定收到数据总长度 序号作用 双端告知各自序号 协议栈自动重发机制 大致流程 ack等待时间如何调整 是…

动态链接那些事

1、为什么要动态链接 1.1 空间浪费 对于静态链接来说&#xff0c;在程序运行之前&#xff0c;会将程序所需的所有模块编译、链接成一个可执行文件。这种情况下&#xff0c;如果 Program1 和 Program2 都需要用到 Lib.o 模块&#xff0c;那么&#xff0c;内存中和磁盘中实际上就…

WEB3 solidity 带着大家编写测试代码 操作订单 创建/取消/填充操作

好 在我们的不懈努力之下 交易所中的三种订单函数已经写出来了 但是 我们只是编译 确认了 代码没什么问题 但还没有实际的测试过 这个测试做起来 其实就比较的麻烦了 首先要有两个账号 且他们都要在交易所中有存入 我们还是先将 ganache 的虚拟环境启动起来 然后 我们在项目…

【计算机组成原理】考研真题攻克与重点知识点剖析 - 第 1 篇:计算机系统概述

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术有限&#xff…

关于算法复杂度的几张表

算法在改进今天的计算机与古代的计算机的区别 去除冗余 数据点 算法复杂度 傅里叶变换

WebSocket实战之五JSR356

一、前言 前几篇WebSocket例子服务端我是用NodeJS实现,这一篇我们用Java来搭建一个WebSocket服务端&#xff0c;从2011年WebSocket协议RFC6455发布后&#xff0c;大多数浏览器都实现了WebSocket协议客户端的API,而对于服务端Java也定义了一个规范JSR356,即Java API for WebSoc…

软件工程与计算总结(二)软件工程的发展

本章开始介绍第二节内容&#xff0c;主要是一些历史性的东西~ 一.软件工程的发展脉络 1.基础环境因素的变化及其对软件工程的推动 抽象软件实体和虚拟计算机都是软件工程的基础环境因素&#xff0c;它们能从根本上影响软件工程的生产能力&#xff0c;而且是软件工程无法反向…

设计模式(包括Spring)、贯穿项目梳理与源码知识点

目标&#xff1a;高复用性&#xff0c;高内聚&#xff0c;低耦合 目的&#xff1a;高可读性&#xff0c;重用性&#xff0c;可靠性 类的六种关系 依赖&#xff0c;类中用到了对方&#xff0c;没有对方连编译都通不过&#xff0c;如下的几种关系全部是依赖关系泛化/继承&…

05_对象性能模式

对象性能模式 面向对象很好地解决了“抽象”的问题,但是必不可免地要付出定的代价。对于通常情况来讲&#xff0c;面向对象的成本大都可以忽略计。但是某些情况&#xff0c;面向对象所带来的成本必须谨慎处理。 典型模型&#xff1a; SingletonFlyweight Singleton 单件模式…