linux c 多进程fork基本用法及阻塞和非阻塞方式回收

2024-06-18 22:32

本文主要是介绍linux c 多进程fork基本用法及阻塞和非阻塞方式回收,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!



一、基本用法


#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>#include <sys/types.h>
#include <unistd.h>#include <iostream>using namespace std;/*  多进程的基本用法  */
int main()
{pid_t ret;if ( (ret=fork()) < 0 ){fprintf(stderr,"fork fail. ErrNo[%d],ErrMsg[%d]\n",errno,strerror(errno));}else if ( 0==ret ){fprintf(stdout,"** child  process run. pid:[%6d], ppid:[%6d],ret:[%6d]  **\n",getpid(),getppid(),ret);//exit(0);  //  可以结束掉子进程,那么程序将不会再运行最后一行的printf,原因在于:fork之后的代码父子进程都可见都会执行,通过if可以控制父子进程进行执行不同的内容,原理在于fork不同于其他函数他返回两个值,给父进程返回的是子进程的pid,给子进程自己返回的是0,失败返回 -1,于是当在if中碰到exit自然子进程就结束了}else{fprintf(stdout,"** parent process run. pid:[%6d], ppid:[%6d],ret:[%6d]  **\n",getpid(),getppid(),ret);}printf("========== last line.  pid:[%6d], ppid:[%6d],ret:[%6d] ==========\n",getpid(),getppid(),ret);
}


二、阻塞方式回收进程防止僵尸进程产生



#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>using namespace std;/*  循环创建多个进程,并通过wait阻塞的方式回收进程  */
int main()
{int p_num=5;/* 使用循环创建多个进程 */for ( int i=0; i < p_num; i++ ){int ret = 0;if ( (ret = fork()) < 0 ){fprintf(stderr,"create child process error\n");}else if ( 0 == ret ){printf("child process [%d], pid[%4d] ppid[%4d]\n",i,getpid(),getppid());sleep(i);exit(0);}}printf("main pid[%4d] ppid[%4d]\n",getpid(),getppid());/* 回收进程,否则在主程序未结束之前,子进程不回收则变成了僵尸进程(主进程结束后僵尸的子进程的父进程会变成init有init回收令系统没有僵尸进程),若主进程是常驻进程则会产生很多僵尸进程 */pid_t child_pid;int status;for ( int i=0; i< p_num; i++ ){if ( (child_pid = wait(&status)) > 0 )printf("pid[%4d] revoked, status[%d]\n",child_pid,status);elsefprintf(stderr,"wait error ErrNo[%d],ErrMsg[%s]\n",errno,strerror(errno));}/* wait()方式回收进程是阻塞式的,即wait会一直等待直到回收了进程,程序才回执行随后的代码 */printf("revoked over\n");sleep(5);printf("all over\n");
}/*
一、使用time ./a.out运行
]# time a.out
child process [0], pid[9925] ppid[9924]
child process [1], pid[9926] ppid[9924]
child process [2], pid[9927] ppid[9924]
main pid[9924] ppid[24077]
child process [3], pid[9928] ppid[9924]
child process [4], pid[9929] ppid[9924]
pid[9925] revoked, status[0]
pid[9926] revoked, status[0]
pid[9927] revoked, status[0]
pid[9928] revoked, status[0]
pid[9929] revoked, status[0]
revoked over
all overreal    0m9.013s
user    0m0.000s
sys     0m0.008s结论:1.time可以统计程序运行的时间,其中real值就是程序从调用到结束花费的时间2.子进程总共sleep时间是max(0,1,2,3,4),父进程因为是阻塞方式所以等子进程都结束后再sleep了5秒,总共9秒3.进程间是并发执行的,所以总共的sleep时间是其中的最大值,而非累加二、ps查看进程
]# ps -ef | grep -i a.out
root    9924 24077  0 16:04 pts/10   00:00:00 a.out
root    9927  9924  0 16:04 pts/10   00:00:00 a.out
root    9928  9924  0 16:04 pts/10   00:00:00 a.out
root    9929  9924  0 16:04 pts/10   00:00:00 a.out
root    9931  9508  0 16:04 pts/6    00:00:00 grep -i a.out
]# ps -ef | grep -i a.out
root    9924 24077  0 16:04 pts/10   00:00:00 a.out
root    9928  9924  0 16:04 pts/10   00:00:00 a.out
root    9929  9924  0 16:04 pts/10   00:00:00 a.out
root    9933  9508  0 16:04 pts/6    00:00:00 grep -i a.out结论:1.随着时间的推移,子进程被回收,进程数减少2.相比不回收进程的情况,在主进程的整个生命周期中没有出现僵尸进程的出现三、注释掉回收的代码运行]# time ./a.out
child process [0], pid[10591] ppid[10590]
child process [1], pid[10592] ppid[10590]
child process [2], pid[10593] ppid[10590]
child process [3], pid[10594] ppid[10590]
main pid[10590] ppid[24077]
child process [4], pid[10595] ppid[10590]
all overreal    0m5.012s
user    0m0.000s
sys     0m0.008s结论:1.相比之前的阻塞回收,本次主进程和五个子进程虽为父子关系,但在系统调用过程中是并发执行的2.故最终耗时将是父进程和子进程花费时间的最大值 5 秒四、ps查看进程]# ps -ef | grep -i a.out
root   10461 24077  0 16:12 pts/10   00:00:00 a.out
root   10462 10461  0 16:12 pts/10   00:00:00 [a.out] <defunct>
root   10463 10461  0 16:12 pts/10   00:00:00 [a.out] <defunct>
root   10464 10461  0 16:12 pts/10   00:00:00 [a.out] <defunct>
root   10465 10461  0 16:12 pts/10   00:00:00 a.out
root   10466 10461  0 16:12 pts/10   00:00:00 a.out
root   10468  9508  0 16:12 pts/6    00:00:00 grep -i a.out]# ps -ef | grep -i a.out
root   10461 24077  0 16:12 pts/10   00:00:00 a.out
root   10462 10461  0 16:12 pts/10   00:00:00 [a.out] <defunct>
root   10463 10461  0 16:12 pts/10   00:00:00 [a.out] <defunct>
root   10464 10461  0 16:12 pts/10   00:00:00 [a.out] <defunct>
root   10465 10461  0 16:12 pts/10   00:00:00 [a.out] <defunct>
root   10466 10461  0 16:12 pts/10   00:00:00 a.out
root   10470  9508  0 16:12 pts/6    00:00:00 grep -i a.out]# ps -ef | grep -i a.out
root   10472  9508  0 16:12 pts/6    00:00:00 grep -i a.out结论:1.不回收的情况下,在主进程未结束sleep的5秒内,子进程逐渐exit结束后变为僵尸进程(<defunct>便是僵尸进程)2.因为不同进程sleep时间不同故,随着时间推移,僵尸进程逐渐增多3.五秒后主进程结束,僵尸进程被系统init接管,init回收僵尸进程4.系统中没有一个a.out的进程了
*/



三、非阻塞方式回收进程



#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>#include <iostream>using namespace std;/**子进程退出的时候,会发送SIGCHLD信号,默认的POSIX不响应,*所以,用该函数处理SIGCHLD信号便可,同时使用signal设置处理信号量的规则(或跳转到的函数)*/
void sig_handle( int num )
{int status;pid_t pid;while( (pid = waitpid(-1,&status,WNOHANG)) > 0){if ( WIFEXITED(status) ){printf("child process revoked. pid[%6d], exit code[%d]\n",pid,WEXITSTATUS(status));}elseprintf("child process revoked.but ...\n");}
}/*  循环创建多进程,采用非阻塞方式回收进程  */
int main()
{int p_num=5;signal(SIGCHLD, sig_handle);/* 使用循环创建多个进程 */for ( int i=0; i < p_num; i++ ){pid_t ret = 0;if ( (ret = fork()) < 0 ){fprintf(stderr,"fork fail. ErrNo[%d],ErrMsg[%s]\n",errno,strerror(errno));}else if ( 0 == ret ){printf("child process [%d], pid[%6d] ppid[%6d]\n",i,getpid(),getppid());sleep(i);exit(0);}}printf("main pid[%6d] ppid[%6d]\n",getpid(),getppid());/* 不再显式,阻塞方式回收进程,子进程退出的时候,会发送SIGCHLD信号,通过signal()注册处理信号的方法 */sleep(5);printf("sleep 1 over\n");sleep(5);printf("sleep 2 over\n");sleep(5);printf("sleep 3 over\n");sleep(5);printf("sleep 4 over\n");sleep(5);printf("all over\n");
}
/*
]# time ./a.out
child process [0], pid[ 19711] ppid[ 19710]
child process [1], pid[ 19712] ppid[ 19710]
child process [2], pid[ 19713] ppid[ 19710]
main pid[ 19710] ppid[ 15683]
child process [3], pid[ 19714] ppid[ 19710]
child process [4], pid[ 19715] ppid[ 19710]
child process revoked. pid[ 19711], exit code[0]
sleep 1 over
child process revoked. pid[ 19712], exit code[0]
sleep 2 over
child process revoked. pid[ 19713], exit code[0]
sleep 3 over
child process revoked. pid[ 19714], exit code[0]
sleep 4 over
child process revoked. pid[ 19715], exit code[0]
all overreal    0m4.009s
user    0m0.000s
sys     0m0.008s1.问题:如果程序末尾只有一个sleep函数的话,信号处理完后就会结束主进程的sleep,于是主程序结束,程序最终仅回收了一个进程,故而达不到回收所有进程的目的
2.不是解决方法的方法:加了5个sleep于是,可以看到程序将五个进程都回收了
3.总结1)异步回收依靠的是信号,子进程结束会发信号SIGCHLD,系统默认不处理,我们可通过signal()自定义该信号的处理方法2)因为信号也就是中断,是不确定时间点产生的,所以没有阻塞等待的过程,等到信号发生了去处理就行,于是达到了不阻塞情况的进程回收3)信号的到达会唤醒将主进程从sleep状态唤醒,于是导致并不能回收所有进程4)由于3)的原因,需要进一步研究,今天不早了,还有事就先到这5)如果主进程是常驻进程该方法就没有问题了
4.附1)信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关2)非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。3)
*/




四、结论非阻塞方式与阻塞方式的对比


1)阻塞方式便于流程控制,清晰易懂

2)非阻塞方式因为依靠的是信号,信号的发出和处理会唤醒主进程的sleep,这将导致假如主进程中用sleep控制程序将出现不sleep的情况。如果写上两个sleep的确可以解决不sleep的问题,但是在没有子进程回收时又多sleep了一次。

3)进程的回收主要原因是,子进程使用了return或者exit结束后,在主进程结束前,没人为已经结束的子进程收尸,子进程便成了僵尸进程。如果主进程会结束,那么这些僵尸进程将在主进程结束后由系统init为其收尸,如果主进程是常驻进程,那么可想而知,僵尸进程将越来越多,这就是进程回收的必要性。

4)经测试,所创建的子进程如果不结束,阻塞式回收进程会立即返回,错误码10,含义是没有子进程,而不是死等需要回收的子进程产生,所以,这种情况下,主进程是常驻进程,使用阻塞回收不会影响主进程的循环工作。


五、关于处理僵尸进程,感觉看下文就够了


http://www.cnblogs.com/wuchanming/p/4020463.html


或者,看这个也有点感觉的


http://www.cnblogs.com/pied/p/4441734.html


这篇关于linux c 多进程fork基本用法及阻塞和非阻塞方式回收的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

离心萃取机废旧磷酸铁锂电池回收工艺流程

在废旧磷酸铁锂电池的回收工艺流程中,离心萃取机主要应用于萃取除杂的步骤,以提高回收过程中有价金属(如锂)的纯度。以下是结合离心萃取机应用的废旧磷酸铁锂电池回收工艺流程: 电池拆解与预处理 拆解:将废旧磷酸铁锂电池进行拆解,分离出电池壳、正负极片、隔膜等部分。破碎与筛分:将正负极片进行破碎处理,并通过筛分将不同粒径的物料分开,以便后续处理。 浸出与溶解 浸出:采用适当的浸出工艺(如二段式逆

基本知识点

1、c++的输入加上ios::sync_with_stdio(false);  等价于 c的输入,读取速度会加快(但是在字符串的题里面和容易出现问题) 2、lower_bound()和upper_bound() iterator lower_bound( const key_type &key ): 返回一个迭代器,指向键值>= key的第一个元素。 iterator upper_bou

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta