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

相关文章

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

Python中logging模块用法示例总结

《Python中logging模块用法示例总结》在Python中logging模块是一个强大的日志记录工具,它允许用户将程序运行期间产生的日志信息输出到控制台或者写入到文件中,:本文主要介绍Pyt... 目录前言一. 基本使用1. 五种日志等级2.  设置报告等级3. 自定义格式4. C语言风格的格式化方法

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

HTTP 与 SpringBoot 参数提交与接收协议方式

《HTTP与SpringBoot参数提交与接收协议方式》HTTP参数提交方式包括URL查询、表单、JSON/XML、路径变量、头部、Cookie、GraphQL、WebSocket和SSE,依据... 目录HTTP 协议支持多种参数提交方式,主要取决于请求方法(Method)和内容类型(Content-Ty

Python ORM神器之SQLAlchemy基本使用完全指南

《PythonORM神器之SQLAlchemy基本使用完全指南》SQLAlchemy是Python主流ORM框架,通过对象化方式简化数据库操作,支持多数据库,提供引擎、会话、模型等核心组件,实现事务... 目录一、什么是SQLAlchemy?二、安装SQLAlchemy三、核心概念1. Engine(引擎)

Java中HashMap的用法详细介绍

《Java中HashMap的用法详细介绍》JavaHashMap是一种高效的数据结构,用于存储键值对,它是基于哈希表实现的,提供快速的插入、删除和查找操作,:本文主要介绍Java中HashMap... 目录一.HashMap1.基本概念2.底层数据结构:3.HashCode和equals方法为什么重写Has