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

相关文章

VScode连接远程Linux服务器环境配置图文教程

《VScode连接远程Linux服务器环境配置图文教程》:本文主要介绍如何安装和配置VSCode,包括安装步骤、环境配置(如汉化包、远程SSH连接)、语言包安装(如C/C++插件)等,文中给出了详... 目录一、安装vscode二、环境配置1.中文汉化包2.安装remote-ssh,用于远程连接2.1安装2

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

虚拟机与物理机的文件共享方式

《虚拟机与物理机的文件共享方式》文章介绍了如何在KaliLinux虚拟机中实现物理机文件夹的直接挂载,以便在虚拟机中方便地读取和使用物理机上的文件,通过设置和配置,可以实现临时挂载和永久挂载,并提供... 目录虚拟机与物理机的文件共享1 虚拟机设置2 验证Kali下分享文件夹功能是否启用3 创建挂载目录4

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

Linux之软件包管理器yum详解

《Linux之软件包管理器yum详解》文章介绍了现代类Unix操作系统中软件包管理和包存储库的工作原理,以及如何使用包管理器如yum来安装、更新和卸载软件,文章还介绍了如何配置yum源,更新系统软件包... 目录软件包yumyum语法yum常用命令yum源配置文件介绍更新yum源查看已经安装软件的方法总结软

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Linux:alias如何设置永久生效

《Linux:alias如何设置永久生效》在Linux中设置别名永久生效的步骤包括:在/root/.bashrc文件中配置别名,保存并退出,然后使用source命令(或点命令)使配置立即生效,这样,别... 目录linux:alias设置永久生效步骤保存退出后功能总结Linux:alias设置永久生效步骤

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存