fork函数详解_全纯函数是什么

fork函数详解_全纯函数是什么从最简单(基础)的一个例子说起,应该说是最基础而不是简单,下面的这个最基础的例子其实并不简单,因为有很多细节。我们需要从fork函数的定义开始说起:man手册官方定义thisfunctioncreatesanewprocess.Thereturnvalueisthezerointhechildandtheprocess-idnumberofthechildintheparent,or-1uponerror.这个函数创建一个新的进程。在子进

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

从最简单(基础)的一个例子说起,应该说是最基础而不是简单,下面的这个最基础的例子其实并不简单,因为有很多细节。
我们需要从fork函数的定义开始说起:

man 手册官方定义
this  function  creates a new process. The return value is the zero in
the child and the process-id number of the child in the parent,
or -1 upon error.
这个函数创建一个新的进程。在子进程中返回0,在父进程中返回子进程的进程id,发生错误则返回-1。

Jetbrains全家桶1年46,售后保障稳定

第一次看的时候非常的奇怪,一个函数返回两次?是的,在调用fork后,fork函数后面的所有代码会执行两遍。下面通过一个例子来解释fork函数定义的含义。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/** *最基础的fork例子 **/
int main(int argc, char const *argv[])
{ 
   
    pid_t pid;
    //判断1
    if ((pid=fork()) < 0)
    { 
   
        perror("fork error");
    }
    //判断2
    else if (pid == 0)//子进程
    { 
   
         printf("child getpid()=%d\n", getpid());
    }
    //判断3
    else if(pid > 0)//父进程
    { 
   
        printf("parent getpid()=%d\n", getpid());
    }

    return 0;
}

这是一个最基本的例子。我们先运行一下代码。

parent getpid()=13725
child getpid()=13726

非常的神奇,两个判断的代码都执行了。这是非常不可思议的,但fork函数确实实现了这样的功能。也就是在fork函数后面的代码都会执行2遍。 这就是为什么两个判断都会被执行的原因。
现在来梳理一下成功fork的执行流程
第一步: pid=fork(),如果成功那么pid就有一个非0正值。否则返回-1。
第二步: 因为pid>0,所以进入判断3。这是在父进程。
第三步: 父进程的代码执行完了,程序又会把fork后面的函数再执行一遍,此时pid的值变为0,所以进入判断2。

这里要解释下getpid()函数,先看下他的定义:

man手册官方定义:
DESCRIPTION
       The getpid() function shall return the process ID of the calling process.

RETURN VALUE
       The getpid() function shall always be successful and no return value is reserved to in‐
       dicate an error.

getpid()获取调用他的进程的id,如果失败不会有返回值。

也就是说哪个进程调用getpid,就返回这个进程的pid。所以如果你想要获得子进程的pid,那么只要在判断2里面调用getpid就可以了。

令人迷惑的pid_t pid变量

还有一个需要解释的就是我们自己定义的这个pid_t pid变量。这个变量非常具有迷惑性。因为在很多书上都取这个名字,好像这个变量就是进程的pid。这是错误的。
这个变量的真正含义应该是return value of the fork(),也就是fork函数的返回值,而且返回值并不一定就是pid,也可能是错误值-1。
下面是这个变量的一种错误用法,试图用这个变量来输出父子进程的pid。

int main(int argc, char const *argv[])
{ 
   
    pid_t pid;
    //判断1
    if ((pid=fork()) < 0)
    { 
   
        perror("fork error");
    }
    //判断2
    else if (pid == 0)
    { 
   
        printf("child pid=%d\n",pid);
    }
    //判断3
    else
    { 
   
        printf("parent pid=%d\n",pid);
    }

    return 0;
}

这个一个错误的例子,程序的目的是试图通过pid变量来获取父子进程的pid。
输出结果:

parent pid=15077
child pid=0

这种做法是完全错误的,不要这么干!因为这个pid变量的命名实在是太有迷惑性了。判断2里面的pid会永远输出0,而判断3里面的pid并不是父进程的pid,实际上是子进程的pid。正确的做法是通过第一个例子的getpid函数来获取。

pid_t pid这个变量的唯一作用就是用来做三个条件判断。
pid_t pid这个变量的唯一作用就是用来做三个条件判断。
pid_t pid这个变量的唯一作用就是用来做三个条件判断。
不要拿他做别的事情。也许取名叫process_status会比较好。

父子进程的调用流程

前面的例子展示了fork最基本的用法。下面通过一个例子来解释fork函数的调用细节。

int main(){ 
   
	fork();//fork1
	fork();//fork2
	printf("hello\n");
	return 0;
}

问printf一共打印了几次?创建了几个子进程?
第一个问题非常好回答,执行一下程序就知道了。一共是输出了4次hello字符串。为什么是4次呢?可以做下面的图分析:
在这里插入图片描述

假设我们的main进程pid是1001,注意看左边的1,2,4进程其实都是main进程1001。进程3,6是同一个进程1002。所有一共有1001,1002,1003,1004四个进程。也就是只要数叶子节点就行了。其中1个是main进程,其它3个是子进程。有多少个进程就输出多少次hello字符串。也就是只有4,5,6,7执行了printf。

int main(){ 
   
	fork();//fork1
	fork();//fork2
	fork();//fork3
	printf("hello\n");
	return 0;
}

如果程序改成这样,结果是类似的,一共有8个进程,其中一个main进程,7个子进程。如果在程序最后加上sleep函数让进程一直存在,那么你可以在进程管理器里面查看到对应的进程和pid,如下图。
在这里插入图片描述

进程管理

既然生成了子进程,那么就需要管理这些子进程,那么谁来管呢?当然是谁生成谁负责。这其中有非常多的细节。看下面这个基本例子。通过getppid(有两个p)获取父进程的pid。

int main(){ 
   
	fork();//fork1
	fork();//fork2
	printf("ppid is %d\n",getppid());
	printf("hello\n");
	return 0;
}

输出结果

ppid =4564
hello 
ppid =26134
hello 
ppid =26135
ppid =26134
hello 
hello 

这个结果顺序是随机的,我们发现第一个ppid好像有点奇怪,另外三个pid都是差不多的。这个进程实际是main进程,他的parent是shell,因为我们的程序是在shell里面执行的。而shell的pid是4564(每次系统启动可能发生变化)。这还是比较好理解的。但有时候输出可能是下面这种情况。

ppid =4564
hello 
ppid =26570
ppid =26570
hello 
hello 
ppid =1
hello 

最后一个ppid是1,这是怎么回事呢?这是因为父进程在子进程结束之前先结束了。子进程没有了父进程,变成了孤儿进程。这时候init进程就会把这个孤儿进程收为他的“养子”,而init进程就成了孤儿进程的养父。在Linux系统中,init进程的id为1。这也就是ppid为1的原因。
可见父进程是没有办法在自己消亡的时候回收子进程的。

参考:Unix/Linux fork前传

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/220322.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • java中的invoke方法_java中的反射,invoke方法详解

    java中的invoke方法_java中的反射,invoke方法详解展开全部就是调用类中的方法e68a843231313335323631343130323136353331333365646239,最简单的用法是可以把方法参数化,invoke(class,method)比如你Test类里有一系列名字相似的方法setValue1、setValue2等等。可以把方法名存进数组v[],然后循环里invoke(test,v[i]),就顺序调用了全部setValue如:…

    2022年6月3日
    33
  • iocomp入门教程-以MFC中iplotx为例

    iocomp入门教程-以MFC中iplotx为例最近要做一个项目需要绘制曲线,为了节省时间,就选用了iocomp控件,可网上相关的教程极少,官方给的文档还是比较详尽,但缺少具体的前期准备步骤,在初次接触这个控件很容易蒙,所以我写下这篇,给入门者以便利。用到的材料:              iocomp激活成功教程版(目前常见的为V3和V4,两个版本按喜好选择吧~            

    2022年7月17日
    14
  • sharepoint 2013 附件控件FileUpload怎样检验是否为图片的方法「建议收藏」

    sharepoint 2013 附件控件FileUpload怎样检验是否为图片的方法

    2022年1月26日
    38
  • gcc命令使用_c调用cmd并执行命令

    gcc命令使用_c调用cmd并执行命令gcc命令基本操作Hello基本操作编译汇编链接ELF文件分析Hello基本操作准备工作#include<stdio.h>//此程序很简单,仅仅打印一个HelloWorld的字符串。intmain(void){printf(“HelloWorld!\n”);return0;}hello.i文件编译汇编将编译生成的hello.s文件汇编生成目标文件hello.oGCC的选项-c使GCC在执行完汇编后停止,生成目标文件

    2022年10月13日
    5
  • opencv的sift_opencv sift

    opencv的sift_opencv sift《SIFT原理与源码分析》系列文章索引:http://blog.csdn.net/xiaowei_cqu/article/details/8069548尺度空间理论自然界中的物体随着观测尺度不同有不同的表现形态。例如我们形容建筑物用“米”,观测分子、原子等用“纳米”。更形象的例子比如Google地图,滑动鼠标轮可以改变观测地图的尺度,看到的地图绘制也不同;还有电影中的拉伸镜头等等…

    2022年10月15日
    2
  • 为什么L1正则化导致稀疏解「建议收藏」

    一、从数据先验的角度首先你要知道L1范式和L2范式是怎么来的,然后是为什么要把L1或者L2正则项加到代价函数中去.L1,L2范式来自于对数据的先验知识.如果你认为,你现有的数据来自于高斯分布,那么就应该在代价函数中加入数据先验P(x),一般由于推导和计算方便会加入对数似然,也就是log(P(x)),然后再去优化,这样最终的结果是,由于你的模型参数考虑了数据先验,模型效果当然就更好.哦对了,如果你…

    2022年4月12日
    70

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号