导航网站php,网站模板为什么不好,上海大型网站开发公司,微信小程序怎么批量删除目录
前言#xff1a;
可重入函数
重谈进程等待和优化 前言#xff1a;
在前文#xff0c;我们已经介绍了信号产生#xff0c;信号保存#xff0c;信号处理的主题内容#xff0c;本文作为信号处理的续篇#xff0c;主要是介绍一些不那么重要的内容#xff0c;第一个…目录
前言
可重入函数
重谈进程等待和优化 前言
在前文我们已经介绍了信号产生信号保存信号处理的主题内容本文作为信号处理的续篇主要是介绍一些不那么重要的内容第一个点是可重入函数第二个点是在信号处理这里的进程等待。
那么话不多说我们进入主题吧 可重入函数 大家对于链表的增删查改已经是什么熟悉了吧在Linux中如果我们有一个链表我们要对链表执行的操作是insert那么从main函数进去之后进行p-next这步的时候突然进行信号捕捉的话这里肯定有人会有疑问的了为什么会进行信号捕捉呢
如果是这个进程的时间片到了呢OS要调度其他的进程了那么从用户态转到了内核态此时进行信号的捕捉所以捕捉到了信号就又会插入节点原本插入的节点是Node1的这下多出来了一个Node2节点可是我们甚至没有办法去调用node2节点这造成的问题是什么呢
造成的问题是十分严重的即内存泄漏。
那么这种函数会造成内存泄漏或者说是涉及到了共享资源的比如堆的开辟比如全局变量比如静态变量都是共享的涉及到了以上共享资源的函数就不满足可重入性。
那么我们应该如何实现具备可重入性的函数呢 不使用全局或静态变量因为全局或静态变量是共享的多个线程同时访问可能会导致数据不一致。如果必须使用则必须通过适当的同步机制如互斥锁来保护这些变量。 不调用不可重入的函数如果一个函数调用了另一个不可重入的函数那么它本身也会变成不可重入的。 不返回指向静态分配的内存的指针因为这可能导致多个线程返回相同的指针从而访问和修改相同的内存区域。 不使用任何依赖于特定线程环境的资源例如某些I/O操作如标准输入/输出可能依赖于特定的线程环境如果它们不是线程安全的那么调用这些操作的函数就不是可重入的。
其实方式很简单我们只需要保证该函数没有使用共享资源即可反例是stl里面的容器几乎所有的容器都涉及到了堆上的开辟比如扩容等操作那么这些所有函数就不是可重入的。
这个点我们了解一下即可。 重谈进程等待和优化
有人好奇咯这里明明介绍的是信号和进程等待有什么关系呢这里更厉害的其实还有涉及到了编译器的优化方面并且编译器优化也分为了几个层次我们先从进程等待入手。
我们先看一段代码
int gflag 0;void changedata(int signo)
{std::cout get a signo: signo , change gflag 0-1 std::endl;gflag 1;
}int main() // 没有任何代码对gflag进行修改
{signal(2, changedata);while(!gflag); // while不要其他代码std::cout process quit normal std::endl;
} 可以发现发送2号信号之后发现gflag确实是从0变成了1不然while循环也是不能结束的。
好了现在我们来谈谈编译器优化的问题在C里连续的拷贝构造 构造编译器是直接会优化成直接构造的这个我们是十分清楚的。
那么g也是个编译器吧它也会进行相应的优化我们先man一下g 这一行代表的优化成都默认的优化是O0我们也可以在编译的时候修改优化程度可是我们光是知道优化是没有用的我们还需要介绍一下上面代码的硬件部分知识 对于cpu来说它执行的运算一般是分为逻辑运算和算数运算的对于上面while里面的判断执行的就是逻辑运算不管是哪种运算将值放到寄存器的时候都是从物理内存里面放吧
好现在是cpu从物理内存里面得到对应的数据当然这个过程是由OS来完成的那么每次都要从物理内存拿这个数据是不是有点麻烦OS了所以编译器在这里如果开了优化那么就不让cpu从物理内存里面获取gflag的值了直接就让cpu从寄存器里面获取也就是说从运行函数开始寄存器里面只有一个值也就是第一次while判断里的gflag的值那么也就代表我们即便是修改了gflag的值cpu也不知道因为它只从寄存器里面读取 将对应的makefile修改一下然后我们试试 发现的现象是嘿退不出去了也就印证了编译器在这里的优化。
这种现象叫做没有保持内存的可见性。
那么我们如何保持内存的可见性呢很简单只需要用到一个关键字就可以了volatile即可这个在const部分我们也有使用该国这里加一个关键字的事儿所以就不过多演示了。 好了现在我们来谈谈进程的等待。
我们知道父进程一般是会等待子进程的吧并且父进程要收集子进程的退出信息吧
可是父进程怎么知道子进程什么时候退出呢
实际上子进程退出的时候是会给父进程发送相关信号的该信号是SIGCHLD 该信号是对应的17号信号。
默认的行为其实是Ign也就是忽略的意思。
void notice(int signo)
{std::cout get a signal: signo pid: getpid() std::endl;pid_t rid waitpid(-1, nullptr, 0);if (rid 0){std::cout wait child success, rid: rid std::endl;}else if (rid 0){std::cout wait child success done std::endl;}
}
void DoOtherThing()
{std::cout DoOtherThing~ std::endl;
}
int main()
{signal(SIGCHLD, notice);pid_t id fork();if (id 0){std::cout I am child process, pid: getpid() std::endl;sleep(3);exit(1);}// fatherwhile (true){DoOtherThing();sleep(1);}return 0;
}对于上面的代码是我们信号处理部分熟知的我们通过这个代码验证了子进程退出的时候的的确确会发送17号信号可是我们在信号处理的时候也知道了信号如果还没有处理完是会自动屏蔽当前多出来的信号的也就是我们创建多个子进程的事儿 for (int i 0; i 10; i){pid_t id fork();if (id 0){std::cout I am child process, pid: getpid() std::endl;sleep(3);exit(1);}}
做了以上的修改之后我们发现 创建子进程之后父进程等待子进程是一个一个等待的这也验证了之前所说的信号被屏蔽之后会继续处理被屏蔽的信号。
那么你说有没有进程是一直不退出的呢如果创建了一个永远不退出的子进程怎么办假设这里存在5个要退出的子进程5个不知道是否退出的子进程难道父进程要一个一个的问你是否要退出吗
这是不现实的如果父进程真的傻傻的去等待了导致的结果就是两个进程永远退出不了只能被系统回收。因为造成了阻塞所以我们可以将等待方式设置一下变成非阻塞等待
void notice(int signo)
{std::cout get a signal: signo pid: getpid() std::endl;while (true){pid_t rid waitpid(-1, nullptr, WNOHANG); // 阻塞啦-- 非阻塞方式if (rid 0){std::cout wait child success, rid: rid std::endl;}else if (rid 0){std::cout wait child success done std::endl;break;}else{std::cout wait child success done std::endl;break;}}
}
并且当我们对于17号信号设置成了忽略子进程也不会出现僵尸问题了。
以上是对于信号处理的补充。 感谢阅读