Linux创建多个子进程并通过捕获SIGCHLD信号进行非阻塞回收

2024-05-02 20:38

本文主要是介绍Linux创建多个子进程并通过捕获SIGCHLD信号进行非阻塞回收,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我们通过fork函数创建多个子进程,并通过exec函数族在子进程中进行其他的工作,但是为了避免僵尸进程,我们要对子进程进行回收。常用的回收方式是wait或者waitpid进行阻塞回收,因为如果非阻塞回收很难把握时机,而阻塞回收将导致父进程无法进行其他的工作。通过子进程状态改变后会发送一个SIGCHLD信号这一机制,我们可以在父进程中将这一信号进行捕获然后进行非阻塞的回收子进程并保证能够回收所有的,也不需要通过sleep函数去强制保证异步。

通过捕获SIGCHLD信号进行回收子进程最害怕的就是父进程还没有设置完捕获函数,子进程全部都死翘翘了,然后父进程就等不到SIGCHLD信号,无法开始回收进程。为了避免这种情况,一般的解决方法是首先对子进程进行一个sleep等待父进程设置捕获函数,我觉得这种做法十分低效,我想到的解决方式是在fork函数前就对SIGCHLD信号进行屏蔽,等父进程设置好捕获函数后再解除屏蔽,这样就不会错过SIGCHLD信号啦。

另一方面因为未决信号集只是一个简单的位图,只能保存有该信号,不能保存该信号发送了多少次,因此我们每次回收进程都要把已经死亡的所有进程进行回收,因为有可能很多子进程一起死亡,这些信号一起发过来,我们不能一个信号只回收一个子进程。

代码如下:
Utils.h:封装了一些简单的操作,简化代码,实现放在文末

#ifndef LINUX_UTILS_H
#define LINUX_UTILS_H#include <string>
#include <initializer_list>
#include <signal.h>/*!* 检查系统调用返回值* @param x 返回值* @param msg 错误提示语句* @param y 错误状态,默认为-1*/
bool check_error(int x, const std::string &msg = "error", int y = -1);
/*!* 清零mask,并将il中的信号加入到mask中* @param mask* @param il*/
void add2mask(sigset_t *mask, std::initializer_list<int> il);
/*!* 将il中的信号从mask中删除* @param mask* @param il*/
void del2mask(sigset_t *mask, std::initializer_list<int> il);/*!* 向阻塞信号集里面添加信号* @param oldset* @param il*/
void add2procmask(std::initializer_list<int> il);/*!*  从阻塞信号集里面删除信号* @param il*/
void del2procmask(std::initializer_list<int> il);#endif //LINUX_UTILS_H

创建子进程并回收

int &wait_child_num() {static int num = 0;return num;
}void wait_child(int signum) {pid_t pid;int wstatus;while ((pid = waitpid(0, &wstatus, WNOHANG)) > 0) {++wait_child_num();if (WIFEXITED(wstatus)) {cout << "process[" << pid << "] exited with " << WEXITSTATUS(wstatus) << endl;} else {cout << "process[" << pid << "] was terminated by signal " << WTERMSIG(wstatus) << endl;}}
}int test_wait() {int idx;pid_t pid;constexpr int N = 5;/*!* 在fork前应该将SIGALRM信号加入阻塞信号集,否则父进程还没有来得及设置信号捕捉函数回收子进程,他们全都死亡了,回收了个寂寞*/add2procmask({SIGCHLD});for (idx = 0; idx < N; ++idx) {pid = fork();check_error(pid, "fork error");if (pid == 0)break;}if (idx == N) {//父进程//注册SIGALRM信号捕捉函数struct sigaction act, oldact;act.sa_flags = 0;add2mask(&act.sa_mask, {SIGINT, SIGQUIT, SIGTSTP});act.sa_handler = wait_child;check_error(sigaction(SIGCHLD, &act, &oldact), "sigaction error");//解除对SIGALRM的屏蔽del2procmask({SIGCHLD});cout << "begin to wait for children" << endl;while (wait_child_num() < N);check_error(sigaction(SIGCHLD, &oldact, nullptr), "sigaction error");} else {my_sleep(idx, 0);}
}

其中mysleep函数是我自己实现的sleep函数,如果有兴趣可以看我的另一篇博客:Linux信号实现精确到微秒的sleep函数:通过sigsuspend函数解决时序竞态问题

通过wait_child_num返回一个局部静态变量num引用获取回收了的子进程的个数,虽然在捕获函数中使用静态变量将导致捕获函数不再是一个可重入函数,但是因为在我的代码中只有捕获函数会对num进行写操作,因此不会发生全局变量异步IO,而且在捕获信号期间会对SIGCHLD信号屏蔽(通过设置sigaction结构体的sa_flags为0),也不用担心会发生重入。

之所以将其变成一个局部静态变量而不是直接使用一个静态变量是 Effective C++ 条款18:让接口容易被正确使用的建议,尽可能使用局部静态变量,因为这样一方面可以避免名字污染,另一方面可以避免初始化次序问题,当在多个文件中的时候确保使用到该变量时能够被初始化。

通过测试和查阅APUE,我发现子进程的阻塞信号集和父进程是一致的,但是未决信号集子进程会清零。

Utils.cpp:工具函数的实现,非常简单

#include "utils.h"using std::string;bool check_error(int x, const string &msg, int y) {if (x == y) {perror(msg.c_str());exit(1);}return true;
}void add2mask(sigset_t *mask, std::initializer_list<int> il) {check_error(sigemptyset(mask), "sigemptyset error");for (auto signum : il) {check_error(sigaddset(mask, signum), "sigaddset error");}
}void del2mask(sigset_t *mask, std::initializer_list<int> il) {for (auto signum : il) {check_error(sigdelset(mask, signum), "sigdelset error");}
}void add2procmask(std::initializer_list<int> il) {sigset_t mask;add2mask(&mask, il);check_error(sigprocmask(SIG_BLOCK, &mask, nullptr), "sigprocmask error");
}void del2procmask(std::initializer_list<int> il) {sigset_t mask;add2mask(&mask, il);check_error(sigprocmask(SIG_UNBLOCK, &mask, nullptr), "sigprocmask error");
}

这篇关于Linux创建多个子进程并通过捕获SIGCHLD信号进行非阻塞回收的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/955115

相关文章

Linux镜像文件制作方式

《Linux镜像文件制作方式》本文介绍了Linux镜像文件制作的过程,包括确定磁盘空间布局、制作空白镜像文件、分区与格式化、复制引导分区和其他分区... 目录1.确定磁盘空间布局2.制作空白镜像文件3.分区与格式化1) 分区2) 格式化4.复制引导分区5.复制其它分区1) 挂载2) 复制bootfs分区3)

JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)

《JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)》:本文主要介绍如何在IntelliJIDEA2020.1中创建和部署一个JavaWeb项目,包括创建项目、配置Tomcat服务... 目录简介:一、创建项目二、tomcat部署1、将tomcat解压在一个自己找得到路径2、在idea中添加

Java利用Spire.Doc for Java实现在模板的基础上创建Word文档

《Java利用Spire.DocforJava实现在模板的基础上创建Word文档》在日常开发中,我们经常需要根据特定数据动态生成Word文档,本文将深入探讨如何利用强大的Java库Spire.Do... 目录1. Spire.Doc for Java 库介绍与安装特点与优势Maven 依赖配置2. 通过替换

C#实现将Excel工作表拆分为多个窗格

《C#实现将Excel工作表拆分为多个窗格》在日常工作中,我们经常需要处理包含大量数据的Excel文件,本文将深入探讨如何在C#中利用强大的Spire.XLSfor.NET自动化实现Excel工作表的... 目录为什么需要拆分 Excel 窗格借助 Spire.XLS for .NET 实现冻结窗格(Fro

C# GC回收的方法实现

《C#GC回收的方法实现》本文主要介绍了C#GC回收的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 一、什么是 GC? 二、GC 管理的是哪部分内存? 三、GC 什么时候触发?️ 四、GC 如何判断一个对象是“垃圾

JAVA SpringBoot集成Jasypt进行加密、解密的详细过程

《JAVASpringBoot集成Jasypt进行加密、解密的详细过程》文章详细介绍了如何在SpringBoot项目中集成Jasypt进行加密和解密,包括Jasypt简介、如何添加依赖、配置加密密钥... 目录Java (SpringBoot) 集成 Jasypt 进行加密、解密 - 详细教程一、Jasyp

Linux服务器数据盘移除并重新挂载的全过程

《Linux服务器数据盘移除并重新挂载的全过程》:本文主要介绍在Linux服务器上移除并重新挂载数据盘的整个过程,分为三大步:卸载文件系统、分离磁盘和重新挂载,每一步都有详细的步骤和注意事项,确保... 目录引言第一步:卸载文件系统第二步:分离磁盘第三步:重新挂载引言在 linux 服务器上移除并重新挂p

java创建xls文件放到指定文件夹中实现方式

《java创建xls文件放到指定文件夹中实现方式》本文介绍了如何在Java中使用ApachePOI库创建和操作Excel文件,重点是如何创建一个XLS文件并将其放置到指定文件夹中... 目录Java创建XLS文件并放到指定文件夹中步骤一:引入依赖步骤二:创建XLS文件总结Java创建XLS文件并放到指定文件

Linux下屏幕亮度的调节方式

《Linux下屏幕亮度的调节方式》文章介绍了Linux下屏幕亮度调节的几种方法,包括图形界面、手动调节(使用ACPI内核模块)和外接显示屏调节,以及自动调节软件(CaliseRedshift和Reds... 目录1 概述2 手动调节http://www.chinasem.cn2.1 手动屏幕调节2.2 外接显

Linux(centos7)虚拟机没有IP问题及解决方案

《Linux(centos7)虚拟机没有IP问题及解决方案》文章介绍了在CentOS7中配置虚拟机网络并使用Xshell连接虚拟机的步骤,首先,检查并配置网卡ens33的ONBOOT属性为yes,然后... 目录输入查看ZFhrxIP命令:ip addr查看,没有虚拟机IP修改ens33配置文件重启网络Xh