【Linux】volatile | SIGCHLD | 多线程概念

简介: 【Linux】volatile | SIGCHLD | 多线程概念

1. volatile

在vscode中,创建signal.c文件

b1329a4cb3a548ac91d537d443ce2a44.png

故意在while中没有写代码块,让编译器认为在main中,quit只会被检测

510c236a355f4be8bf52bd6244d6e24e.png

运行可执行程序后,当输入 2号信号时,调用自定义方法将quit置为1,跳出while循环


编译器优化

编译器有对应的编译优化级别 -O1 -O2 -O3

b317bbd4ab074df895a042ebd664020d.png

在makefile中,添加-O2的优化级别

66264f156b7a41389b60f231cef8650b.png

再次执行可执行程序时,输入2号信号,只调用了对应的自定义方法,说明进入main中的while循环 无法停止

68fa458cd3f846389106c3864d437d10.png

全局变量被加载到内存中

while循环判断实际上是一种计算,会在CPU去执行的

进行计算时,将内存中的数据load到CPU中的寄存器上,然后才对quit进行真假判断

内存中有当前进程的代码和数据,CPU中有对应的PC指针去指向

若while循环条件满足,pc指针继续指向while循环的代码


10f113c3c966418eae0c1af564b39593.png

若while循环条件不满足,则pc指针会向下移动,指向下一条语句,并向后执行


正常来说,每次都要尝试数据从内存load到CPU的过程

在main函数中 quit是没有被修改的,只是被检测,编译器发现quit变量没有被修改,就不会重复把数据从内存load到CPU中

因此编译器会优化,只需第一次把数据从内存load到CPU中,后续只需要检测寄存器中的数据即可


fa721e2a39584607bb7155eb5fced8b1.png

所以刚开始quit为0,将0传给CPU中,后续输入2号信号后,调用自定义方法,quit变为1,

但是在CPU中依旧quit为0,修改了内存中的quit,那CPU中quit就无法影响内存的quit了

一直使用quit为0,所以while循环无法退出


所以要告诉编辑器,保证每次检测,都要从内存中进行数据读取,不要用寄存器中的数据

为了解决这个问题,使用volatile


58eeaf4c67d241f4a704e0d6a125f93c.png

使quit变为volatile修饰的全局变量

volatile作用:杜绝对quit变量进行寄存器级别的优化,保证内存可见性

56b7cbdd21d64fb6a8b88771a2106e26.png


再次运行可执行程序,输入2号信号,跳出while循环,执行main中的printf打印

2.SIGCHLD信号

子进程在运行时会退出,若父进程不关心子进程退出,子进程就会变成僵尸状态

父进程要使用 wait/waitpid去等待子进程 回收僵尸,获取子进程的退出结果

即父进程进行阻塞式等待(什么都不干,就等待子进程的退出结果)

父进程主动检测--------因为子进程退出了,父进程暂时不知道


子进程要退出时,会向父进程发信号 SIGCHLD

父进程对于该信号的处理动作是SIG_DFL 什么都不做

验证SIGCHLD的存在

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
pid_t id;
void handler(int signo)
{
    sleep(5);
    printf("捕捉到一个信号:%d,who:%d\n",signo,getpid());
    //-1代表等待任意一个子进程
    pid_t ret=waitpid(-1,NULL,0);
    if(ret>0)
    {
        printf("wait success,ret:%d,id:%d\n",ret,id);
    }
}
int main()
{
    signal(SIGCHLD,handler);//自定义捕捉
   id=fork();
  if(id==0)
  {
    //子进程
    int cnt=5;
    while(cnt)
    {
        printf("我是子进程,我的pid是:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
    exit(1);
  }
  //父进程
  while(1)
  {
    sleep(1);
  }
    return 0;
}


实现一个自定义方法,当子进程退出时,会向父进程发送信号SIGCHLD

调用对应的自定义方法,打印出对应的信号以及父进程的pid值


27f580b0112248878a2c3a19737cb47a.png


运行可执行程序后,who的pid值就是父进程的pid

17号信号就是SIGCHLD

同时通过waitpid返回的pid值与子进程的pid值相同


通过for循环创建出10个子进程,若10个子进程发送信号,处理信号需要一个一个处理,所以当发送一个信号时,可能暂时被保留下来,但是父进程只有一个比特位 pending位图保留信号,当再次保留信号时,pending位图再次被置为1

,把上次信号覆盖掉,造成信号丢失,最后处理信号时可能比发送信号的数量少


b125aced06fa43ce98de7dcbc5543c69.png

有多少子进程,就回收几次,若没有子进程就退出即可

3. 多线程

多线程概念

1.线程是一个执行分支,执行粒度比进程更细,调度成本更低

2.线程是进程内部的一个执行流

3.线程是CPU调度的基本单位,进程是承担分配系统资源的基本实体


下面将会对于这些概念进行解析


理解概念

什么是多线程

37a8c904fd044c879accb813d5d0321a.png

创建子进程时,只创建PCB,创建出来的PCB继续指向父进程的地址空间

代码区假设有很多函数存在,让不同的PCB执行不同的函数

相当于在一个进程内部包含多个执行流,指向同一个进程内的不同代码区域

每个PCB都是单独的线程

线程在地址空间内运行,所以该线程属于进程

调度成本低

多个线程之间使用的是同一个地址空间和页表

若为新的进程,则还需再次找到新的地址空间和页表并进行切换


局部性原理

CPU内部存在一个硬件cache

把一部分数据预先加载到缓冲区里,提高整机的效率

如CPU正在访问第100行代码,未来有很大概率访问101行,

所以一旦访问到第100行就把100行附近的数据全部load到内存中或者CPU的cache中


多线程在执行代码和数据时,依旧属于这个进程,CPU里面的cache会缓存各种各样的数据

261775586121490c9b56e91b1fc2fbb3.png

若进行线程切换,因为都属于同一个进程,cache中缓存的数据是不变的

083e5823917c4488bdb3979b6d2cf24d.png

若进行进程切换,把当前缓存的数据设为失效,cache要重新加载当前的代码和数据

调度成本更低,体现在不用对cache进行切换

什么叫做进程

0b1f844489794e4383c00ded88724c9d.png

task_struct 叫做执行流

进程包含 一大堆的执行流、地址空间、页表以及该进程对应的代码和数据

所以进程是承担分配系统资源的基本实体


相关文章
|
8月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
6月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
250 67
|
5月前
|
NoSQL Linux 编译器
GDB符号表概念和在Linux下获取符号表的方法
通过掌握这些关于GDB符号表的知识,你可以更好地管理和理解你的程序,希望这些知识可以帮助你更有效地进行调试工作。
215 16
|
5月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
108 20
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
84 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
82 0
|
7月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
241 4
|
8月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
139 17
|
3月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
144 0
|
3月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。