以循环创建5个进程为例,给出如下代码,分析其错误

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    int i;
    pid_t pid;
    printf("xxxxxxxxxxx\n");

    for (i = 0; i < 5; i++)
    {
        pid = fork( ); 
        if(pid == -1)
        {
            printf("process of %u creat process failurely!\n",getpid( ));
            perror("fork");
        }
        else if(pid == 0)
        {
            printf("I'am %dth child , pid = %u\n", i+1, getpid());
        }

        else
        {
            printf("I'am parent, pid = %u\n",getpid());
        }
    }

        printf("yyyyyyyyyy\n");

    return 0;
}

分析:首先在shell中执行该文件时,由终端进程fork产生一个子进程来执行该程序,然后在for循环体中,子进程在创建一个个的孙进程。在上述for循环体中,i=0时,父进程创建了一个子进程,此时父进程与子进程的i都为0(刚fork后两个的i相等,但是以后不一定相等,它们各自独立)。此时有两个进程(父、子进程)都会开始向下执行,即后面的代码都一样的执行,各个进程一直执行到return语句后,各个进程才会自动终止(结束)。上述,在for循环体中创建的子进程,又会在下一次循环中继续去创建子进程,因此最终并不仅仅创建的是5个进程,而是共创建了25-1个进程,总共25个进程。如果循环n次,则总共为2n个进程。

因此,需要在循环的过程,保证子进程不再执行fork ,因此当(fork() == 0)时,子进程应该立即break;才正确(即跳出循环体)。

 

练习:通过命令行参数指定创建进程的个数,如:第1个子进程休眠0秒打印:“我是第1个子进程”;第2个进程休眠1秒打印:“我是第2个子进程”;第3个进程休眠2秒打印:“我是第3个子进程。”

通过该练习掌握框架:循环创建n个子进程,使用循环因子i对创建的子进程加以区分。

//代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[ ])
{
    if(argc < 2)
    {
        printf("./a.out 5\n");
        exit(1);
    }

    int i;
    pid_t pid;
    printf("xxxxxxxxxxx\n");
    long int a = strtol(argv[1],NULL,10);  //将字符串转化为10进制整数

    for (i = 0; i < a; i++)
    {
        pid = fork( );  //创建子进程
        if(pid == -1)
        {
            printf("process of %u creat process failurely!\n",getpid( ));  
            perror("fork");
        }  //判断创建进程是否成功,如果当次循环创建不成功,则不结束该进程,进行下一次循环,再次尝试创建(这样等效于少了一次循环)。
        else if(pid == 0)  //如果为子进程,则跳出循环
        {
            break;
        }

        else    //否则(父进程),不执行操作,进入下一次循环
             ;
    }

    sleep(i);  //通过i值来区分进程,可见父进程睡眠时间最久,为a秒,最先创建的子进程睡眠时间最少,为0秒。

    if(i < 5)
        printf("I'am the %dth child process, the ID = %u\n",i+1,getpid( )); //子进程输出
    else
        printf("I'am parent process, the ID = %u\n",getpid( ));  //父进程输出

    return 0;
}

[root@localhost fork]# ./fork_test 5  //shell终端fork产生子进程来运行这一程序

xxxxxxxxxxx 
I'am the 1th child process, the ID = 16507
I'am the 2th child process, the ID = 16508
I'am the 3th child process, the ID = 16509
I'am the 4th child process, the ID = 16510
I'am the 5th child process, the ID = 16511
I'am parent process, the ID = 16506
[root@localhost fork]#

分析:之所以要引入sleep函数,来使各个进程睡眠,是为了确保父进程最后结束(即最后执行return),且越先创建的子进程越先能够结束。而在创建进程后,每个进程的i值都由自己维护,都要从创建处开始执行自己的代码,从而i值发生改变。因此就可以用i来区分是哪一个进程,从而越先创建的进程睡眠时间越少,第i个子进程睡眠时间为i-1秒。下面深度分析sleep函数,代码如下:

//与上面的代码相比,只是去掉了一行内容: sleep(i);  因此不再列出,其执行结果如下:
[root@localhost fork]# ./fork_test 5
xxxxxxxxxxx
I'am parent process, the ID = 16702
I'am the 3th child process, the ID = 16705
[root@localhost fork]# I'am the 1th child process, the ID = 16703
I'am the 2th child process, the ID = 16704
I'am the 5th child process, the ID = 16707
I'am the 4th child process, the ID = 16706
pwd   //正常执行shell中的pwd命令(前面标签已经输出)
/mnt/hgfs/share/01_process_test/fork
[root@localhost fork]#

 

分析:由上可以看出,在没有sleep( )函数的控制下,每个进程的结束先后顺序是随机的,没法控制的。在上述程序执行过程中,总共参与了7个进程:shell终端进程、父进程(由shell终端fork产生)和5个子进程(由父进程fork产生),这7个进程对CPU的抢占是公平的(随机的),无法预测。注意一点:父进程只能够知道其子进程是否结束,而不能直到其孙进程是什么状态,这7个进程共同使用这一个终端,当shell中执行. /fork_test 5时,shell进程会把前台交给其子进程使用,一旦子进程结束(执行了return后),shell进程知道并马上收回前台,并输出[root@localhost fork]# 光标  等待与用户再次交互(此时shell进程放弃了CPU,将自己阻塞起来,等待用户的命令),但是此时那5个子进程并不一定就结束了,因此未结束的进程将会继续占用CPU,直到执行到return并结束。因此,这些进程的输出结果会在 [root@localhost fork]#的后面。CPU1s内可执行上亿条指令,因此睡1s可以绝对保证进程可以按照希望的顺序执行。