volatile+SIGCHLD信号+可重入函数(了解)

news2024/12/24 2:25:18

索引

  • volatile
      • 1.gcc -O含义及其作用
      • 2.证明其内存可见性
  • 深入理解SIGCHLD信号
      • SIGCHLD总结
  • 可重入函数

volatile

保存内存的可见性,告知编译器,该关键字修饰的变量不允许被优化,对该变量的任何操作都必须在内存中操作。

1.gcc -O含义及其作用

gcc提供了为了满足用户不同程度的优化需求,提供了很多优化选项,用来对(编译时间,目标文件长度,执行效率)进行平衡。下面大致介绍几个优化级别

-O0:不做任何优化,这是默认的编译选项。
-O和-O1:对程序做部分编译优化,对于大函数,优化编译占用稍微多的时间和相当大的内存,编译器会尝试缩小代码的尺寸以及缩短执行时间,但并不执行需要占用大量编译器的优化。
-O2:比O1高级,进行更多的优化。Gcc将执行几乎所有的不包含时间和空间这种的优化,此时编译器不进行循环打开()以及内联函数,与O1比较而言,O2优化增加了编译时间的基础上,提高了生成代码的执行效率。
O3:在O2的基础上做进一步优化。

优化的代码可能给调试带来问题,可能会改变代码的结构,eg:对分支的合并和消除
还有可能给内存操作顺序改变带来问题。eg:对于某些依赖内存操作顺序而进行的逻辑,需要做严格的处理后才能进行优化

2.证明其内存可见性

此时程序的编译:
g++ -o $@ $^ -std=c++11 -O2

int flag = 0;
void handler(int signo)
{
    printf("变量的值已经修改了 flag:%d\n", flag);
    flag = 1;
}
int main()
{
    signal(2, handler);
    while (!flag)
        ;
    printf("process 正常退出\n");

    return 0;
}

在这里插入图片描述
上述代码Gcc编译时进行了O2优化,编译器编译的时候看到循环中没有语句,此时其循环的判断条件除第一次从内存中提取之外,其他时间都是从寄存器提取,因此,当我们CTRL+C时,命名条件的flag = 1,但程序依旧不会跳出循环。
如果我们在一样的代码上添加volatile
在这里插入图片描述
由此可见volatile的作用其保持内存的可见性.

深入理解SIGCHLD信号

父进程创建子进程后,之前讲述的都是父进程用waitpid()和wiat()函数清理僵尸进程,此时既可以阻塞式等待:此时父进程无法进一步处理自己的工作,直到子进程退出。
非阻塞式等待:父进程采取轮循检测的方式等待子进程退出。
实际上在子进程退出的时候会给父进程发送一个信号:SIGCHLD信号,只不过父进程对该信号的默认处理方式是忽略。下面验证一下子进程退出的时候,子进程是否会发送SIGCHLD信号。
实验设计:
父进程fork()创建子进程之后,子进程exit()退出,给SIGCHLD信号设计自定义捕捉函数。

void ChildHandler(int signo)
{
    // 此时阻塞式等待任何一个子进程。
    pid_t id = waitpid(-1, nullptr, 0);
    if (id > 0)
    {
        cout << "父进程等待成功" << endl;
    }
}
int main()
{
    signal(SIGCHLD, ChildHandler);
    pid_t id = fork();
    if (id == 0)
    {
        cout << "我是子进程" << endl;
        sleep(3);
        exit(1);
    }
    while (true)
    {
        cout << "main running..." << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
其实上述的自定义捕捉函数是有bug的,如果此时父进程创建了很多子进程并且子进程同时退出的话,就会有多个SIGCHLD信号被发送,会导致该信号被堵塞,此时就会产生很多僵尸进程

void ChildHandler(int signo)
{
    // 此时阻塞式等待任何一个子进程。
    pid_t id = waitpid(-1, nullptr, 0);
    if (id > 0)
    {
        cout << "父进程等待成功" << endl;
    }
}
int main()
{
    signal(SIGCHLD, ChildHandler);
    for (int i = 0; i < 10; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            int cnt = 10;
            while (cnt)
            {
                cout << "我是子进程,pid: " << getpid() << "  cnt:" << cnt-- << endl;
                sleep(1);
            }
            cout << "子进程退出进入僵尸状态;" << endl;

            exit(1);
        }
    }

    while (true)
    {
        cout << "main running..." << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

由于信号同时在处理时继续发送信号此时信号会被阻塞,所以上述代码会出现僵尸进程。
所以我们循环式读取直到waitpid()调用失败,此时跳出循环表示已经全部等待完成,就不会出现僵尸进程。

void ChildHandler(int signo)
{
    while (true)
    {

        // 此时阻塞式等待任何一个子进程。
        pid_t id = waitpid(-1, nullptr, 0);
        if (id > 0)
        {
            cout << "父进程等待成功" << endl;
        }
    }
}

在这里插入图片描述
此时并不会出现僵尸进程,但是此情况针对的是子进程同时退出,子进程也有可能不同时退出。如下所示。

void ChildHandler(int signo)
{
    while (true)
    {

        // 此时阻塞式等待任何一个子进程。
        pid_t id = waitpid(-1, nullptr, 0);
        if (id > 0)
        {
            cout << "父进程等待成功" << endl;
        }
    }
}
int main()
{
    signal(SIGCHLD, ChildHandler);
    for (int i = 0; i < 10; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {

            int cnt = 10;

            if (i < 6)
            {
                cnt = 5;
            }
            else
            {
                cnt = 50;
            }
            while (cnt)
            {
                cout << "我是子进程,pid: " << getpid() << "  cnt:" << cnt-- << endl;
                sleep(1);
            }
            cout << "子进程退出进入僵尸状态;" << endl;

            exit(1);
        }
    }

    while (true)
    {
        cout << "main running..." << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
此时就会出现子进程一直在运行,但是父进程不会运行自己的代码,因为此时自定义函数中的等待方式是阻塞式等待,此时只要我们将阻塞式等待变成非阻塞式等待即可。

void ChildHandler(int signo)
{
    while (true)
    {

        // 此时非阻塞式等待任何一个子进程。
        pid_t id = waitpid(-1, nullptr, WNOHANG);
        if (id > 0)
        {
            cout << "父进程等待成功 child pid:" << getpid() << endl;
        }
        else if (id == 0)
        {
            cout << "还有子进程没有退出,父进程要去忙自己的事情了" << endl;
            break;
        }
        else
        {
            cout << "父进程等待所有的子进程结束" << endl;
            break;
        }
    }
}
int main()
{
    signal(SIGCHLD, ChildHandler);
    for (int i = 0; i < 10; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {

            int cnt = 10;

            if (i < 6)
            {
                cnt = 5;
            }
            else
            {
                cnt = 20;
            }
            while (cnt)
            {
                cout << "我是子进程,pid: " << getpid() << "  cnt:" << cnt-- << endl;
                sleep(1);
            }
            cout << "子进程退出进入僵尸状态;" << endl;

            exit(1);
        }
    }

    while (true)
    {
        cout << "我是父进程,我正在运行:" << getpid() << endl;
        cout << "main running..." << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

SIGCHLD总结

上述是使用该信号的自定义捕捉函数,那么如果我们不使用他呢?
sigactionSIGCHLD的处理动作设置成SIG_IGN(忽略),这样fork()出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程,系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的,但这是一个特例,该方法只对Linux可用,但不保证在其他的UNIX系统上都可用。
这里就不做模拟了。
但是设置了SIG_IGN后,可不仅仅是自动释放子进程这一个内容,这是一个函数,函数可以修改父进程的相关属性,进而被子进程继承下去,如果没有设置忽略的话,子进程在没有waitpid()的情况下是会产生僵尸进程的,但是设置SIG_IGN就不会产生僵尸进程,子进程会自动释放!

可重入函数

当两个不同的控制流程调用同一个函数,访问同一个局部变量或参数造成程序的错乱,该函数就是不可冲入函数。
符合下列条件之一的就是不可冲入函数:

1.调用了malloc 或 free,因为malloc也是用全局链表来管理堆的
2.调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

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

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

相关文章

JVM垃圾回收机制和常用算法(简洁版)

垃圾收集 (Garbage Collection,GC) 垃圾收集主要是针对堆和方法区进行。程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的&#xff0c;只存在于线程的生命周期内&#xff0c;线程结束之后就会消失&#xff0c;因此不需要对这三个区域进行垃圾回收。 判断一个对象是…

QTday5作业

Tcp服务器 源文件 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//给服务器指针实例化对象server new QTcpServer(this); //此时就创建了一个服务器}Widg…

Android数据结构和算法总结-字符串相关高频面试题算法

前言 周末闲来无事&#xff0c;在七月在线上看了看字符串相关算法的讲解视频&#xff0c;收货颇丰&#xff0c;跟着视频讲解简单做了一下笔记&#xff0c;方便以后翻阅复习同时也很乐意分享给大家。什么字符串在算法中有多重要之类的大路边上的客套话就不多说了&#xff0c;直…

linux删除了业务进程正在使用的文件,又不能停止进程,如何释放文件占用的磁盘空间

一台linux分区使用率告警&#xff0c;同事为了清理空间&#xff0c;通过du -sh *查到应用的日志文件占用很高&#xff0c;他直接rm删除了日志文件。但df -h看分区空间并没有释放。 执行lsof | grep delete可以看到刚刚删除的文件因为被正在运行的进程调用所以并没有释放磁盘空间…

优维产品最佳实践:如何设计流水线?

前言&#xff1a;我们上期介绍了什么是CI以及CI的重要性&#xff0c;本期目标就是学习如何设计流水线&#xff0c;流水线是一种用于自动化软件开发和部署的工具链&#xff0c;它可以将软件开发过程中的各个步骤组织成一个连续的流程&#xff0c;从而提高开发效率和软件质量。在…

安装虚拟机+安装/删除镜像

安装虚拟机 注意&#xff0c;官网可能无法登录&#xff0c;导致无法从官网下载&#xff0c;就自己去网上搜靠谱的下载&#xff0c;我用的16.2.3 删除镜像 Vm虚拟机怎么删除已经创建的系统&#xff1f;Vm虚拟机创建好之后iso删除方法 - 系统之家 (xitongzhijia.net) 安装镜像…

Python中内建模块和标准库的工作原理? - 易智编译EaseEditing

Python中的内建模块和标准库是Python编程语言的一部分&#xff0c;它们提供了大量的功能和工具&#xff0c;可用于各种任务和应用程序。这些模块和库通常是由Python官方维护的&#xff0c;因此在任何标准Python安装中都是可用的。 以下是内建模块和标准库的工作原理的概述&…

数据结构——单调队列

单调队列 单调队列的概念和操作过程 概念&#xff1a; 单调队列和单调栈在操作上有相似之处&#xff0c;但因为单调队列是队列&#xff0c;所以多了一项特殊操作&#xff0c;即头部的元素可以出队&#xff0c;相当于滑动窗口向后滑动。这头部的出队操作就相当于淘汰&#xff0c…

需求管理做不好,项目成功难保障

你是否曾感觉客户的要求总是突然冒出来&#xff1f;或是觉得自己跟不上订单的节奏&#xff1f;不了解产品的需求会让你手忙脚乱&#xff0c;如果没有足够的资源来应对&#xff0c;甚至会损害你的业务。 本文将向你介绍需求管理的流程&#xff0c;以及如何在业务中使用它。 项目…

【洛谷算法题】P5703-苹果采购【入门1顺序结构】

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5703-苹果采购【入门1顺序结构】&#x1f30f;题目描述&#x1f30f;输入格式&a…

BCSP-玄子Share-Java框基础_反射

一、反射 1.1 反射介绍 Java反射&#xff1a;在编译时不确定哪个类被加载&#xff0c;而在程序运行时才加载、探知、使用 1.1.1 Java 程序的运行过程 1.1.2 反射及其作用 反射是指能够在运行时&#xff0c;观察并修改自己运行时(Runtime)行为的特性 Java 反射机制主要提供了…

verilog语法之case casez casex

在rtl仿真中&#xff0c;有四种状态&#xff0c;分别是0、1、x&#xff08;unknown values&#xff09;和z&#xff08;high-impedance values&#xff09;。 case 结构体中&#xff1a;0&#xff0c;1&#xff0c;X与Z是四种不同的状态&#xff0c;case条件比较时会检测比较双…

2023年03月 C/C++(七级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C编程&#xff08;1~8级&#xff09;全部真题・点这里 第1题&#xff1a;走出迷宫 当你站在一个迷宫里的时候&#xff0c;往往会被错综复杂的道路弄得失去方向感&#xff0c;如果你能得到迷宫地图&#xff0c;事情就会变得非常简单。 假设你已经得到了一个n*m的迷宫的图纸&a…

BCSP-玄子Share-Java框基础_工厂模式/代理模式

三、设计模式 3.1 设计模式简介 软件设计中的三十六计是人们在长期的软件开发中的经验总结是对某些特定问题的经过实践检验的特定解决方法被广泛运用在 Java 框架技术中 3.1.1 设计模式的优点 设计模式是可复用的面向对象软件的基础可以更加简单方便地复用成功的设计和体系…

【管理运筹学】第 7 章 | 图与网络分析(2,最小支撑树问题)

文章目录 引言二、最小支撑树问题2.1 树的定义及其基本性质2.2 图的支撑树2.3 最小支撑树的定义及有关定理2.4 最小支撑树算法2.4.1 避圈法&#xff08;KRUSKAL算法&#xff09;2.4.2 反圈法&#xff08;PRIM算法&#xff09;2.4.3 破圈法 写在最后 引言 承接前文&#xff0c;…

宠物电商Chewy第二季度销售额28亿美元,同比增长14.3%

美国宠物电商Chewy公布2023年第二季度财报。报告显示&#xff0c;其Q2季度销售额同比增长14.3%至28亿美元&#xff0c;超出市场预期。 以下为Chewy期内业绩概要&#xff1a; 1.毛利率28.3%&#xff0c;同比增长20个基点 2.净利润有所收窄&#xff0c;同比下降15.2%至1890万美…

QT连接数据库

目录 数据库 数据库基本概念 常用的数据库 SQLite3基础 SQLite特性&#xff1a; QT连接数据库 1.1 QT将数据库分为三个层次 1.2 实现数据库操作的相关方法 sql语句&#xff08;常用&#xff09; 1&#xff09;创建表格 2&#xff09;删除表格 3&#xff09;插入记录 …

应急物资管理系统DW-S300|助力应急物资保障体系建设

国务院新闻办公室于2023年7月25日举行国务院政策例行吹风会&#xff0c;介绍防汛抗旱工作情况&#xff0c;并答记者问。应急管理部副部长、水利部副部长王道席介绍&#xff0c;要推进应急物资保障体系建设。去年&#xff0c;应急管理部会同国家发展改革委、财政部、国家粮食和物…

AIGC专栏5——EasyPhoto AI写真照片生成器 sd-webui插件介绍、安装与使用

AIGC专栏5——EasyPhoto AI写真照片生成器 插件安装与使用 学习前言源码下载地址技术原理储备&#xff08;SD/Control/Lora&#xff09;StableDiffusionControlNetLora EasyPhoto插件简介EasyPhoto插件安装安装方式一&#xff1a;Webui界面安装 &#xff08;需要良好的网络&…

Linux监测进程打开文件

分析问题过程中&#xff0c;追踪进程打开的文件可以在许多不同情况下有用&#xff0c;体现在以下几个方面&#xff1a; 故障排除和调试&#xff1a; 当程序出现问题、崩溃或异常行为时&#xff0c;追踪进程打开的文件可以帮助找出问题的根本原因。这有助于快速定位错误&#x…