• c++并发编程之进程创建(给那些想知道细节的人)


          关于多进程创建,此处只讲解一个函数fork().

    1.进程创建

    先上代码:

     1  #include"iostream"                                                                                                                                        
     2  #include<unistd.h> //unix标准文件
     3    int main()
     4    { 
     5        using namespace std;
     6        pid_t pid;
     7       cout<<"parent have!"<<endl;
     8            pid = fork();//执行fork的时候到底发生了什么?
     9        if(pid == -1)//错误创建
    10        {
    11            perror("fork error");
    12              _exit(1);
    13        }
    14        else if(pid == 0)//子进程
    15        {
    16 
    17            cout<<"i am child,pid = "<<getpid()<<" my parent is:"<<getppid()<<endl;
    18        }
    19        else//父进程
    20        {
    21           // sleep(1);
    22            cout<<"i am parent,pid = "<<getpid()<<" my parent is:"<<getppid()<<endl;
    23        }
    24        cout<<"both have!"<<endl;
    25        return 0;
    26    }

    运行结果:

     程序及结果分析:

       程序分析:

     pid = fork();//执行fork的时候到底发生了什么? 

            这行代码到底发生了什么?我们需要清楚:在这行代码执行之前,如果不考虑系统调用这个层次的进程,那么就只有一个进程,就是main函数所在的进程.,程序的逻辑是顺序逻辑.那么这行代码执行后,将会发生什么?

           "main"进程将会创建一个子进程,确切的讲是复制一个子进程,也就是fork创建子进程,是基于当前进程,复制了其父进程的用户空间(也就是你所看见的上面的代码全部都会被复制).也就是,此刻,存在了两个并行的进程(这个很重要.并不是我们认为的父进程执行完毕了再执行子进程).更确切的讲,下一个时刻,父进程要执行上述代码中的8行以后的代码,子进程也要执行8行以后的代码,究竟谁先执行,看谁抢到了CPU.

            上述程序中,根据fork的返回值,来确定即将要执行什么.从形式上看,明显感觉到fork()的返回值并不是一个值,而是>0的值和等于0的值?难道一个函数可以返回两个返回值?也显然不是,是因为父进程创建了子进程,子进程既然复制了父进程的用户空间,自然来看,有两个pid.执行fork()函数后,父进程的pid为子进程的ID端口号,子进程的pid是0.所以才会根据pid的差异执行不同的动作.

     结果分析:

           从结果可以看到,"parent have!"仅仅打印了一次,而"both have!"执行了两次.这是因为,fork创建的子进程不会执行在此之前的程序,只会执行fork()之后的,而"both have!"并没有限定其为子进程还是父进程需要执行的.

           从结果中可以看出:父进程的部分先得到执行,表明父进程可能先得到返回值,先抢占到cpu(注意这里只是说可能).而且,父进程和子进程的内容均得到了显示(如果是我们单进程的程序,if 和else if 不可能同时执行).

    2.创建多个子进程

     1  #include"iostream"                                                                                                                                        
     2  #include<unistd.h>
     3    int main()
     4    { 
     5        using namespace std;
     6        pid_t pid;
     7       cout<<"parent have!"<<endl;
     8       for(int i = 0;i < 5;i++)
     9       {
    10             pid = fork();//执行fork的时候到底发生了什么?
    11             if(pid == 0)
    12             {
    13                // cout<<"the ID of son "<<i+1<<":"<<getpid()<<endl;
    14                  break;//这个很重要,思考为什么
    15             }
    16       }
    17          
    18        if(pid == -1)//错误创建
    19        {
    20            perror("fork error");
    21              _exit(1);
    22        }
    23        else if(pid == 0)//子进程
    24        {
    25            //sleep(1);
    26            cout<<"i am child,pid = "<<getpid()<<" my parent is:"<<getppid()<<endl;
    27        }
    28        else//父进程
    29        {
    30            sleep(1);
    31            cout<<"i am parent,pid = "<<getpid()<<" my parent is:"<<getppid()<<endl;
    32        }
    33        cout<<"both have!"<<endl;
    34        return 0;
    35    }

    程序运行结果:

     程序及结果分析:

     程序分析:

    假如我们现在想一个父进程创建多个子进程,比如创建5个子进程.我们应该怎么做?

         for(int i = 0;i < 5;i++)
          {
                pid = fork();//执行fork的时候到底发生了什么?
                if(pid == 0)
                {
                   // cout<<"the ID of son "<<i+1<<":"<<getpid()<<endl;
                     break;//结束该子进程
                }
          }

    注意:为什么要在for循环里加了条件判断和break,不加会发生什么?

            我们需要注意的是:我们的父进程创建了子进程之后,子进程和父进程拥有完全一样的用户代码,也就意味着,如果我们不加if判断,那么我们的子进程也将成为新的父进程,创建新的子进程,也就是,每执行一次循环,之前创建的子进程都会成为子进程,也就会会造成指数级增长,最终进程会变成2^5-1个,共31个.当加了break之后呢?当父进程创建完子进程之后,我们说过子进程返回的pid为0,此时,会跳出循环体,阻止了fork()的执行,也就阻止了子进程创建新的子进程(但并不影响子进程执行下面的内容,只是不执行fork而已).而父进程由于得到的是子进程的ID,从而可以继续执行循环体.而新创建的子进程自然也不能执行循环体,这样下来,一个父进程创建了5个等级一样的子进程.

    父子进程共享

    共享遵循的原则:读时共享写时复制原则.

           针对前文中:父进程创建子进程的例子,我们会产生这样的疑问?子进程是完完全全copy父进程的内容吗?  至少现在的Linux系统不是.如果某个变量在子进程是被读的,而不是写的,那么这个变量物理地址空间就是被共享的(注意并不是逻辑空间).如果在子进程中要被修改,那么就会产生一般性的复制行为.

           我们还是需要清楚:父子进程的用户空间基本是完全一样的,但是关于PCB这一块的内核空间并不相同,毕竟每个进程的ID都不同.

  • 相关阅读:
    C#程序调用cmd执行命令(转)
    命名管道跨进程通信实例2(转)
    C#异步编程的实现方式——ThreadPool线程池
    命名管道跨进程通信实例1(转)
    No_16_0324 Java基础学习第二十三天
    mac osx加入全局启动terminal快捷键
    UVa 164
    Android OpenGL加入光照和材料属性
    51系列小型操作系统精髓 简单实现
    ubuntu下安装tomcat
  • 原文地址:https://www.cnblogs.com/shaonianpi/p/11442915.html
Copyright © 2020-2023  润新知