C语言多进程编程

一、进程简介

1、进程是程序的执行。程序是静态的,进程是动态的。
2、进程在内存中有三部分组成:数据段、堆栈段和代码段。
代码段:就是存放程序代码的数据,如果有数个进程运行同一个一个程序,那么它们就可以使用同一个代码段(代码段是可以共享的);
堆栈段:存放的是子程序的返回地址、参数以及程序的局部变量,主要是保存进程的执行的环境,这里用到了栈先进后出的特性,可以看做具有记忆上一次执行的环境。
数据段:存放程序的全局变量 、常数
3、动态数据分配的数据空间
系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段,但是可以使用同一个代码段(代码段是可以共享的)
4、lwp:
线程ID。在用户态的命令(比如ps)中常用的显示方式。

二、创建进程fork()

1、头文件

#include<unistd.h>  
#include<sys/types.h>  

2、函数原型

pid_t fork( void);

pid_t 是一个宏定义

3、返回值

若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1

4、注意点

a、在Linux系统中创建进程有两种方式:一是由操作系统创建,二是由父进程创建进程(通常为子进程)。系统调用函数fork()是创建一个新进程的唯一方式,当然vfork()也可以创建进程,但是实际上其还是调用了fork()函数。fork()函数是Linux系统中一个比较特殊的函数,其一次调用会有两个返回值。

b、调用fork()之后,父进程与子进程的执行顺序是我们无法确定的(即调度进程使用CPU),意识到这一点极为重要,因为在一些设计不好的程序中会导致资源竞争,从而出现不可预知的问题。

c、fork产生子进程的表现就是它会返回2次,一次返回0,顺序执行下面的代码。这是子进程。一次返回子进程的pid,也顺序执行下面的代码,这是父进程。

d、进程创建成功之后,父进程以及子进程都从fork() 之后开始执行,只是pid不同。fork语句可以看成将程序切为A、B两个部分。(在fork()成功之后,子进程获取到了父进程的所有变量、环境变量、程序计数器的当前空间和值)。

e、一般来说,fork()成功之后,父进程与子进程的执行顺序是不确定的。这取决于内核所使用的调度算法,如果要求父子进程相互同步,则要求某种形式的进程间通信。

5、vfork()函数

也用于创建一个进程,返回值与fork()相同。
fork()与vfork()的异同
执行次序:
a、fork():对父子进程的调度室由调度器决定的;
b、vfork():是先调用子进程,等子进程的exit(1)被调用后,再调用父进程;
对数据段的影响:
a、fork():父子进程不共享一段地址空间,修改子进程,父进程的内容并不会受影响。
b、vfork():在子进程调用exit之前,它在父进程的空间中运行,也就是说会更改 父进程的数据段、栈和堆。。即共享代码区和数据区,且地址和内容都是一 样的。

扫描二维码关注公众号,回复: 8871624 查看本文章

三、举例

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <vector>
#include <iostream>
#include<sys/wait.h>

using namespace std;

// 进程退出函数
void print_exit()  
{  
       printf("the exit pid:[%d] \n",getpid() );  
} 

int main()
{
    string sMatch;
    pid_t pid, child_pid;
    vector<string> provList;
    provList.push_back("taskFace");
    provList.push_back("taskObj");
    provList.push_back("taskAction");
    provList.push_back("taskHat");
    provList.push_back("taskOther");

    cout << "Main process,id=" << getpid() << endl;

    // 循环处理"100,200,300,400,500"
    for (vector<string>::iterator it = provList.begin(); it != provList.end(); ++it)
    {
        sMatch = *it;
        atexit( print_exit );
        pid = fork();
        // (*hsq*)子进程退出循环,不再创建子进程,全部由主进程创建子进程,这里是关键所在
        if(pid == 0 || pid == -1)
        {
            break;
        }
    }

    if(pid == -1)
    {
        cout<<"Fail to fork!"<<endl;
        exit(1);
    }
    else if(pid == 0)
    {
        // 这里写子进程处理逻辑
        cout <<"This is children process,id=" << getpid() << ",start to process " << sMatch << endl;
        sleep(10);
        exit(0);
    }
    else
    {
        // 这里主进程处理逻辑
        cout << "This is main process,id=" << getpid() <<",end to process "<< sMatch << endl;

        do 
        {   
            // WNOHANG 非阻塞 轮询 等待带子进程结束
            child_pid = waitpid(pid, NULL, WNOHANG);  
            printf("I am main progress.The pid progress has not exited!\n");
            sleep(2);

        }while(child_pid == 0);
        exit(0);
    }

    return 0;
}

四、代码剖析

执行结果如下:
Main process,id=6803
This is children process,id=6804,start to process taskFace
This is children process,id=6805,start to process taskObj
This is main process,id=6803,end to process taskOther
This is children process,id=6806,start to process taskAction
I am main progress.The pid progress has not exited!
This is children process,id=6807,start to process taskHat
This is children process,id=6808,start to process taskOther
I am main progress.The pid progress has not exited!
I am main progress.The pid progress has not exited!
I am main progress.The pid progress has not exited!
I am main progress.The pid progress has not exited!
the exit pid:[6804] 
the exit pid:[6806] 
the exit pid:[6805] 
the exit pid:[6806] 
the exit pid:[6806] 
the exit pid:[6805] 
the exit pid:[6807] 
the exit pid:[6807] 
the exit pid:[6807] 
the exit pid:[6807] 
the exit pid:[6808] 
the exit pid:[6808] 
the exit pid:[6808] 
the exit pid:[6808] 
the exit pid:[6808] 
I am main progress.The pid progress has not exited!
the exit pid:[6803] 
the exit pid:[6803] 
the exit pid:[6803] 
the exit pid:[6803] 
the exit pid:[6803] 

从结果可以看出,子进程函数中
6804:退出了一次
6805:退出了两次
6806:退出了三次
6807:退出了四次
6808:退出了五次
主进程
6803退出了五次

下面解释一下出现这种问题的主要原因:
1、为什么会出现
6804:退出了一次
6805:退出了两次
6806:退出了三次
6807:退出了四次
6808:退出了五次
的现象
答:因为最下面的6808相当于被fork了5次,后续各个子进程开出的进程发现进程id已经存在,则不会重复创建新的进程,所以各个子进程执行任务只执行一次,但是退出的时候却退出了5次。
2、主进程为什么退出了五次
the exit pid:[6803]
the exit pid:[6803]
the exit pid:[6803]
the exit pid:[6803]
the exit pid:[6803]
答:每个子进程都会开一个新的主进程和子进程。所以会出现主进程退出5次的现象。主进程不会附加标签,但是每个fork的子进程会附加标签
在这里插入图片描述

五、父子进程开始执行的位置

#include <unistd.h> 
#include <sys/types.h>
main () 
{ 
         pid_t pid; 
         printf("hello!\n");  
         pid=fork();
         if (pid < 0) 
                 printf("error in fork!"); 
         else if (pid == 0) 
                 printf("i am the child process, my process id is %d\n ",getpid());
         else 
                 printf("i am the parent process, my process id is %d\n",getpid());
         printf("bye!\n");
} 

这里可以看出parent process执行了printf(“hello!\n”); 而child process 没有执行printf(“hello!\n”);
有一个让人很迷惑的例子:

#include <unistd.h>
#include <sys/types.h>
main () 
{ 
         pid_t pid; 
         printf("fork!");    //printf("fork!\n")
         pid=fork(); 

         if (pid < 0) 
                 printf("error in fork!\n"); 
         else if (pid == 0) 
                 printf("i am the child process, my process id is %d\n",getpid());
         else 
                 printf("i am the parent process, my process id is %d\n",getpid());
}

此时打印输出了两个fork!这不免让人以为是child process从#include处开始执行,所以也执行了printf(“fork!”); 语句。
其实不然,出现这种问题的原因在于:
这就跟Printf的缓冲机制有关了,printf某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列里了,并没有实际的写到屏幕上 。但是,只要看到有\n, 则会立即刷新stdout,因此就马上能够打印了.mian函数(parent process)运行了printf(“fork!”) 后, “fork!”仅仅被放到了缓冲里,再运行到fork时,缓冲里面的 AAAAAA 被子进程(child process)继承了,因此在子进程度stdout缓冲里面就也有了”fork!”。所以,你最终看到的会是 “fork!” 被printf了2次!!! 而mian函数(parent process)运行 printf(“fork!\n”)后,”fork!” 被立即打印到了屏幕上,之后fork到的子进程(child process)里的stdout缓冲里不会有”fork!”内容 因此你看到的结果会是”fork!” 被printf了1次!!!

发布了297 篇原创文章 · 获赞 6 · 访问量 8529

猜你喜欢

转载自blog.csdn.net/qq_23929673/article/details/98885118