【问题标题】:Count lines of each text file in a folder parallel using fork() in C使用 C 中的 fork() 计算文件夹中每个文本文件的行数
【发布时间】:2013-12-02 02:02:16
【问题描述】:

我正在尝试完成作业代码。任务是创建一个程序,计算代码并行运行的文件夹中每个文本文件的行数。所以这是我的代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include "error.c"

char *getFileNameExtension(char *);
int isTxtFile(struct dirent *);
int countLines(struct dirent *);

int main(int argc , char *argv[]){

    //Current Directory
    DIR *currentDir;

    //Current File
    struct dirent *currDirFile;

    pid_t pid;

    int counter = 0;

    /*
    Struct stat "buf" variable , we'll need it
    to check the selected file's st_mode(file type)
    */
    struct stat buf;

    int *lines = malloc(sizeof(int));

    //Opens the directory where the executable exists with the dot(.)
    currentDir = opendir(".");

    if (currentDir){


      /*In the while loop we read each file
      of the current directory until we get a Nullable value */
      while( ( currDirFile = readdir(currentDir) ) != NULL ){

            if ( isTxtFile(currDirFile) == 1 ){

                if ((pid=fork())<0){
                    err_sys("fork error");
                }

                //Child
                else if(pid == 0){

                    exit(0);
                }
                //Father
                else{

                    lines = realloc( lines , sizeof(int)*(counter+1) );
                    lines[counter] = countLines(currDirFile);
                    ++counter;

                }
            }
        if (pid==0){
            printf("Child , pid = %d\n",pid);
        }
        else printf ("Father , pid = %d\n",pid);

      }//End while

      //close(currentDir);

    }

    int i ;

    for(i=0;i<counter;++i){
        printf("%d\n",lines[i]);
    }

    exit(1);
}

 char *getFileNameExtension(char *filename){
    //Gets the memory location of the last dot(.)
    char *ext = strrchr(filename, '.');
    return ext;
}

int isTxtFile(struct dirent *currDirFile){

         struct stat buf;

         /*With the lstat function we try to pass
         the current file into the buf variable*/
        if (lstat(currDirFile->d_name,&buf)<0){

            printf("lstat error");
            return 0;
        }

        /*If the st_mode(type) of the current file is regular
        we print the name of the file*/
        if (S_ISREG(buf.st_mode)){

                char *c = getFileNameExtension(currDirFile->d_name);

                if (c!=NULL){

                    //Check only for .txt files
                    if (strcmp(c,".txt")==0){

                        return 1;

                    }
                    else{

                        return 0;

                    }//End strcmp

                }//End c!=NULL

        }
        else{

           return 0;

        }//END S_ISREG


}

int countLines(struct dirent *currentFile){

    int fd = open(currentFile->d_name,O_RDONLY);
    int n;
    int lines = 0;
    char buf;

    while ((n=read(fd,&buf,1))>0){
        if (buf=='\n') ++lines;
    }

    return (lines);
}

我以这种方式使用了 fork 函数,但我不确定它是否正确,因为当我运行它时,有 4 个父进程正在运行(如我所料),但还有 3 个子进程正在运行。谁能帮助我?实际上,我正在尝试为每个文本文件创建一个进程。

【问题讨论】:

    标签: c unix parallel-processing synchronization fork


    【解决方案1】:

    您最初的问题是您的父/子颠倒了。当从fork 返回时,子级将有一个pid == 0,父级将返回子级的 pid(即一个非零正数)。您当前正在分叉并立即退出孩子,然后打印出错误的信息。当您执行printf 时,pid 的值将是孩子的值(在fork 中返回)而不是它自己的 pid。如果您想要父母的 pid,请使用getpid。当然,孩子的 printf 永远不会打印,因为它已经退出并且不再存在。

    您的另一个大问题是您需要记住,孩子们获得的地址空间与父母不同。虽然孩子最初是父母的副本,但随着时间的推移,两者会发生分歧,孩子所做的更改不会在父母身上看到。

    最后,数数并等待您的孩子。这不仅是一种很好的做法,而且可以防止您的父母在所有孩子之前退出。

    编辑:问题 1

    这是你的第一个问题,看代码中编号的cmets

        while ( ( currDirFile = readdir(currentDir) ) != NULL )
        {
            if ( isTxtFile(currDirFile) == 1 )
            {
                if ((pid = fork()) < 0)
                {
                    err_sys("fork error");
                }
                else
                    if (pid == 0)      //(1) This is the child; it immediately dies
                    {
                        exit(0);
                    }
                    else    //(2) Parent - it does ALL the work
                    {
                        lines = realloc( lines , sizeof(int) * (counter + 1) );
                        lines[counter] = countLines(currDirFile);
                        ++counter;
                    }
            }
    
            if (pid == 0)    //(3) This is reversed again which is why you think
            {                // you have multiple "fathers" when they are really children
                printf("Child , pid = %d\n", pid);
            }
            else
                printf ("Father , pid = %d\n", pid);
        } //End while
    

    输出:

    Father , pid = 17881
    Father , pid = 17882
    Father , pid = 17883
    Father , pid = 17884
    [nearly identical lines skipped]
    114
    14
    7
    3
    128
    12
    

    所以,我们有正确的方法:

    printf ("Father pid = %d\n", getpid());  //(2b)
    
    if (currentDir)
    {
        while ( ( currDirFile = readdir(currentDir) ) != NULL )
        {
            if ( isTxtFile(currDirFile) == 1 )
            {
                if ((pid = fork()) < 0)
                    err_sys("fork error");
    
                if (pid == 0)
                {
                    //printf("Child pid = %d\n", pid);    // (1)
                    printf("Child pid = %d\n", getpid());
                    lines = realloc( lines , sizeof(int) * (counter + 1) );
                    lines[counter] = countLines(currDirFile);
                    ++counter;
                    exit(0);
                }
            }
    
            //printf ("Father pid = %d\n", pid); //(2a)
    
        } //End while
    
        //close(currentDir);
    }
    

    (1) 我们不会在孩子中打印pid 的值,因为它总是为零。相反,我们使用getpid() 获取孩子的真实 pid 值。

    (2) 将父 printf 保持在循环中 (2a) 将使其每次循环都打印,无论它是否是文本文件,这很烦人。所以我们将它移到循环 (2b) 之前,但因为它在 fork 之前,所以 pid 中不会有有效值,所以我们也只使用 getpid() 来获取该值。

    所以不是

    Father pid = 18136
    Father pid = 18136
    Father pid = 18136
    [many identical lines]
    Child pid = 0
    Child pid = 0
    Child pid = 0
    

    我们有想要的输出

    Father pid = 18224
    Child pid = 18225
    Child pid = 18239
    Child pid = 18240
    [etc...]
    

    旁注,出于编译的目的,我假设err_sys 是这样的

    void err_sys(const char *str)
    {
        perror(str);
        exit(1);
    }
    

    问题 2

    您现在没有输出文件中的行数。

    if (pid == 0)
    {
        printf("Child pid = %d\n", getpid());
        lines = realloc( lines , sizeof(int) * (counter + 1) );
        lines[counter] = countLines(currDirFile);
         ++counter;  //(1)
         exit(0);
    }
    
    for (i = 0;i < counter;++i)  //(2)
    {
        printf("%d\n", lines[i]);
    }
    

    因为父母和孩子有单独的记忆,(1)counter的增量永远不会在父母中看到。 Consquent (2) 将被跳过,因为它为零且不打印任何内容。

    所以有两种选择:(1) 从子进程打印或 (2) 设置一些 IPC 机制,以便子进程将其结果报告给父进程,以便它可以打印它们。我会继续,就好像选项 1 是正确的做法一样。

    我们认识到其中一些是无用的,并通过以下方式进行调整:(1) 摆脱数组 - 每个孩子只有 1 个行值 - 以及 (2) 将 printf 移动到它会做一些事情的地方不错。

    //int *lines = malloc(sizeof(int));
    int lines = 0;
    //........
    
    if (pid == 0)
    {
        printf("Child pid = %d\n", getpid());
        //lines = realloc( lines , sizeof(int) * (counter + 1) );
        lines = countLines(currDirFile);
        //++counter;
        printf("%d\n",lines);
        exit(0);
    }
    

    如果丑陋的输出结果是正确的:

    Father pid = 18513
    Child pid = 18514
    114
    Child pid = 18526
    Child pid = 18527
    4
    7
    

    所以清理代码并稍微修饰一下我们得到的输出

    if (pid == 0)
    {
        lines = countLines(currDirFile);
        printf("Child [%d] of parent [%d]: %s = %d lines\n",
                getpid(), getppid(), currDirFile->d_name, lines);
        exit(0);
    }
    

    我们在

    中看到输出结果
    Child [18566] of parent [18565]: output.txt = 114 lines
    Child [18569] of parent [18565]: list.txt = 3 lines
    Child [18574] of parent [18565]: bfile.txt = 9 lines
    Child [18579] of parent [18565]: nums.txt = 4 lines
    Child [18581] of parent [18565]: afile.txt = 9 lines
    Child [18571] of parent [1]: output.txt = 12 lines
    Child [18575] of parent [1]: cfile.txt = 18 lines
    Child [18572] of parent [1]: alphabet.txt = 3 lines
    

    这没关系,但是在printf 中使用getppid 向我们表明,父级在一些子级之前结束,并且在成为孤儿后它们被init process (pid = 1) 继承。所以....

    问题 3

    我们需要防止父级在子级之前结束的竞争条件,并防止使子级成为僵尸。这就是 waitwaitpid 调用的目的。这里的问题是,如果我们将 wait 放在循环中,父级将阻塞并等待每个子级导致一次运行一个子级。

        while ( ( currDirFile = readdir(currentDir) ) != NULL )
        {
            if ( isTxtFile(currDirFile) == 1 )
            {
                if ((pid = fork()) < 0)
                    err_sys("fork error");
    
                if (pid == 0)
                {
                    lines = countLines(currDirFile);
                    printf("Child [%d] of parent [%d]: %s = %d lines\n",
                           getpid(), getppid(), currDirFile->d_name, lines);
                    exit(0);
                }
    
                // ** this is the parent **
                if (wait(NULL) == -1)
                    err_sys("wait");
            }
        } //End while
    

    Child [18656] of parent [18655]: out.txt = 114 lines
    Child [18657] of parent [18655]: input.txt = 7 lines
    Child [18658] of parent [18655]: list.txt = 3 lines
    Child [18659] of parent [18655]: output.txt = 12 lines
    

    由于您需要并行性,因此一种方法是将wait 移到循环之外,并在创建子项时对其进行计数,以便以后获得它们。

    int main(int argc , char *argv[])
    {
        DIR *currentDir;
        struct dirent *currDirFile;
        pid_t pid;
        int counter = 0;
        struct stat buf;
    
        int lines = 0;
        int numChildren = 0;
    
        currentDir = opendir(".");
    
        printf ("Father pid = %d\n", getpid());
    
        if (currentDir)
        {
            while ( ( currDirFile = readdir(currentDir) ) != NULL )
            {
                if ( isTxtFile(currDirFile) == 1 )
                {
                    if ((pid = fork()) < 0)
                        err_sys("fork error");
    
                    if (pid == 0)
                    {
                        lines = countLines(currDirFile);
                        printf("Child [%d] of parent [%d]: %s = %d lines\n",
                               getpid(), getppid(), currDirFile->d_name, lines);
                        exit(0);
                    }
    
                    // ** this is the parent **
                    numChildren++;
                }
            } //End while
    
            closedir(currentDir); // <-- note we use closedir not close for directories
        }
    
        int i;
    
        for (i = 0; i < numChildren; ++i)
        {
            if ((pid = waitpid(-1, NULL, 0)) == -1)
                err_sys("fork error");
            else
                printf("parent [%d] reaped child [%d]\n", getpid(), pid);
        }
    
        exit(0);
    }
    

    导致类似

    Father pid = 18737
    Child [18738] of parent [18737]: out.txt = 114 lines
    Child [18741] of parent [18737]: list.txt = 3 lines
    Child [18743] of parent [18737]: output.txt = 12 lines
    Child [18747] of parent [18737]: cfile.txt = 18 lines
    parent [18737] reaped child [18738]
    parent [18737] reaped child [18741]
    parent [18737] reaped child [18743]
    Child [18754] of parent [18737]: append.txt = 6 lines
    parent [18737] reaped child [18747]
    parent [18737] reaped child [18754]
    

    这是丑陋但正确的。由于大部分输出仅用于演示,您可以对其进行美化。

    【讨论】:

    • 感谢您的回答,但您能否更好地解释最后两行?如果问题只是输出,我不明白
    • @IrishDog - 我已经更新了我的答案,假设你已经自己完成了大部分工作。
    • 好的,您的解决方案非常充分且易于理解。非常感谢!
    猜你喜欢
    • 2017-11-14
    • 1970-01-01
    • 2023-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-15
    • 2016-02-25
    相关资源
    最近更新 更多