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

相关文章

SQL Server中的PIVOT与UNPIVOT用法具体示例详解

《SQLServer中的PIVOT与UNPIVOT用法具体示例详解》这篇文章主要给大家介绍了关于SQLServer中的PIVOT与UNPIVOT用法的具体示例,SQLServer中PIVOT和U... 目录引言一、PIVOT:将行转换为列核心作用语法结构实战示例二、UNPIVOT:将列编程转换为行核心作用语

CSS引入方式和选择符的讲解和运用小结

《CSS引入方式和选择符的讲解和运用小结》CSS即层叠样式表,是一种用于描述网页文档(如HTML或XML)外观和格式的样式表语言,它主要用于将网页内容的呈现(外观)和结构(内容)分离,从而实现... 目录一、前言二、css 是什么三、CSS 引入方式1、行内样式2、内部样式表3、链入外部样式表四、CSS 选

PyTorch高级特性与性能优化方式

《PyTorch高级特性与性能优化方式》:本文主要介绍PyTorch高级特性与性能优化方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、自动化机制1.自动微分机制2.动态计算图二、性能优化1.内存管理2.GPU加速3.多GPU训练三、分布式训练1.分布式数据

Python文件操作与IO流的使用方式

《Python文件操作与IO流的使用方式》:本文主要介绍Python文件操作与IO流的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、python文件操作基础1. 打开文件2. 关闭文件二、文件读写操作1.www.chinasem.cn 读取文件2. 写

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

ubuntu16.04如何部署dify? 在Linux上安装部署Dify的技巧

《ubuntu16.04如何部署dify?在Linux上安装部署Dify的技巧》随着云计算和容器技术的快速发展,Docker已经成为现代软件开发和部署的重要工具之一,Dify作为一款优秀的云原生应用... Dify 是一个基于 docker 的工作流管理工具,旨在简化机器学习和数据科学领域的多步骤工作流。它

Java中 instanceof 的用法详细介绍

《Java中instanceof的用法详细介绍》在Java中,instanceof是一个二元运算符(类型比较操作符),用于检查一个对象是否是某个特定类、接口的实例,或者是否是其子类的实例,这篇文章... 目录引言基本语法基本作用1. 检查对象是否是指定类的实例2. 检查对象是否是子类的实例3. 检查对象是否

Java中的内部类和常用类用法解读

《Java中的内部类和常用类用法解读》:本文主要介绍Java中的内部类和常用类用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录内部类和常用类内部类成员内部类静态内部类局部内部类匿名内部类常用类Object类包装类String类StringBuffer和Stri