C语言 为什么根据使用的子进程的数量不同,会得到不同的结果?

twh00eeo  于 2022-12-03  发布在  其他
关注(0)|答案(1)|浏览(146)

我正在编写一个简单的C程序,它递归地创建子文件,并根据用户的输入,用它们对文件中的所有数字求和。用户可以选择三种预定的文件大小,以及三种可以生成的子文件数量。理论上,可以有任意数量的子文件或任意大小的文件,但为了简单起见,这里只有3个。
我遇到的问题是,无论我使用哪个文件,只有当程序只使用1个子项时,总和才是正确的。如果使用其他数量的子项,例如4个,这个数字很接近,但不完全正确。有人能告诉我是什么导致了这个问题吗?
下面是我认为有问题的代码段:

// C program to demonstrate use of fork() with pipe()
// By: Maxwell Wendlandt
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
    FILE *file;
    int numForks;
    // initialize pipes for up to 4 children
    int fd[4][2];
    // initialize up to 4 processes 
    pid_t pid[4];
    int total = 0;
    int finalResult = 0;
    char fileName[10] = "";
    int fileNum;
    int numLines;

    // ask which file to scan
    printf("Enter file number 1 (1000 nums), 2 (10000 nums) or 3 (100000 nums):\n");
    scanf("%i", &fileNum);
    // chose the file
    switch(fileNum)
    {
    case 1 :
        printf("File 1 selected.\n");
        strcpy(fileName, "file1.dat");
        numLines = 1000;
        break;
    case 2 :
        printf("File 2 selected.\n");
        strcpy(fileName, "file2.dat");
        numLines = 10000;
        break;
    case 3 :
        printf("File 3 selected.\n");
        strcpy(fileName, "file3.dat");
        numLines = 100000;
        break;
    default :
        printf("Enter a valid file number next time.\n");
        return 0;
    }

    // ask how many children (forks)
    printf("Do you want 1, 2 or 4 child processes?\n");
    scanf("%i", &numForks);

    for (int i = 0; i < numForks + 1; i++)
    {
        if (pipe(fd[i]) == -1)
        {
            printf("Error with creating pipe.\n");
            return 1;
        }
    }

    for(int i = 0; i < numForks; i++)
    {
        pid[i] = fork();
        if(pid[i] == -1)
        {
            printf("Error creating child.\n");
            return 1;
        }
        if(pid[i] == 0)
        {
            // children
            int sum = 0, num = 0;
            int start, end;
            file = fopen(fileName, "r");
            
            start = i * (numLines / numForks);
            printf("start: %i\n", start);
            end = ((i + 1) * (numLines / numForks));
            printf("end: %i\n", end);
            fseek(file, (start * 4), SEEK_SET);

            for(int i = start; i < end; i++)
            {
                fscanf(file, "%d", &num);
                printf("num on line %d is: %d\n", i + 1, num);
                sum += num;
            }

            printf("sum in child: %d\n", sum);
        
            write(fd[i][1], &sum, sizeof(sum));
            close(fd[i][1]);
            return 0;
        }
    }
    // parent
    for(int i = 0; i < numForks; i++)
    {
        read(fd[i][0], &total, sizeof(total));
        close(fd[i][0]);
        finalResult += total; 
    }
    
    printf("The grand total: %i\n", finalResult);
    
    for(int i = 0; i < numForks; i++)
    {
        wait(NULL);
    }
    return 0;
}

提前感谢!

3ks5zfa0

3ks5zfa01#

文件的每一行都有一个3位数字。2所以一个1000位数字的文件有1000行。
这意味着每行包含四个五字节-三位数字、回车符和换行符。例如,123\r\n。此处的off-by-two错误

fseek(file, (start * 3), SEEK_SET);

将导致每次寻道偏移,并且每个子级将从比它们应该读取的位置更早的位置读取。如果每行是五个字节,则应为start * 5
旁白:我猜文件中的数字是用零填充的(见下面的生成示例)。
如果是这样的话,fscanf说明符%i可能不合适,因为它充当strtol,基数为0,这意味着数字基数由其第一个字符确定。
当以零填充的数字被解析为八进制时,这可能会导致混乱的结果。例如:

  • 004-八进制,值为4
  • 040-八进制,值为32
  • 400-十进制,值为400
  • 009-八进制,无效值(0)。
  • 011-八进制,值为9

%d会将输入解析为以10为基数的数字。
这有几个问题。

printf("Do you want 1, 2 or 4 child processes?\n");
scanf("%i", &numForks);
for (int i = 0; i < numForks + 1; i++) {
    if (pipe(fd[i]) == -1)
    /* ... */

i < numForks + 1是一个差一的错误。用户也可以输入任意数字。如果fd是通过越界索引访问的,则这将调用Undefined Behaviour
通常,您应该检查更多函数的返回值,如scanffscanffseekwriteread,以确保使用的是有效数据。
首选perrorfprintf(stderr, ...)将有用的错误消息打印到正确的流。
非常粗略的重构:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>

int main(void)
{
    int numForks;
    // initialize pipes for up to 4 children
    int fd[4][2];
    // initialize up to 4 processes
    pid_t pid[4];

    int total = 0;
    int finalResult = 0;
    char fileName[10] = "";
    int fileNum;
    int numLines;

    printf("Enter file number 1 (1000 nums), 2 (10000 nums) or 3 (100000 nums):\n");
    if (1 != scanf("%d", &fileNum)) {
        fprintf(stderr, "Invalid number of files.\n");
        return 1;
    }

    switch (fileNum) {
        case 1:
            printf("File 1 selected.\n");
            strcpy(fileName, "file1.dat");
            numLines = 1000;
            break;
        case 2:
            printf("File 2 selected.\n");
            strcpy(fileName, "file2.dat");
            numLines = 10000;
            break;
        case 3:
            printf("File 3 selected.\n");
            strcpy(fileName, "file3.dat");
            numLines = 100000;
            break;
        default:
            printf("Enter a valid file number next time.\n");
            return 0;
    }

    printf("Do you want 1, 2 or 4 child processes?\n");
    if (1 != scanf("%d", &numForks) || 1 > numForks || numForks > 4 || numForks == 3) {
        fprintf(stderr, "Invalid number of child processes.\n");
        return 1;
    }

    for (int i = 0; i < numForks; i++) {
        if (pipe(fd[i]) == -1) {
            perror("pipe");
            return 1;
        }
    }

    for (int i = 0; i < numForks; i++) {
        pid[i] = fork();

        if (pid[i] == -1) {
            perror("fork");
            return 1;
        }

        // children
        if (pid[i] == 0) {
            int sum = 0, num = 0;
            int start, end;
            FILE *file = fopen(fileName, "r");

            if (!file) {
                fprintf(stderr, "Child %d failed to open ", i + 1);
                perror(fileName);
                return 1;
            }

            start = i * (numLines / numForks);
            end = ((i + 1) * (numLines / numForks));
            printf("start: %d\nend: %d\n", start, end);

            if (-1 == fseek(file, (start * 4), SEEK_SET)) {
                perror("fseek");
                return 1;
            }

            for (int i = start; i < end; i++)
                if (1 == fscanf(file, "%d", &num))
                    sum += num;

            printf("sum in child: %d\n", sum);

            write(fd[i][1], &sum, sizeof sum);
            close(fd[i][1]);
            return 0;
        }
    }

    // parent
    for (int i = 0; i < numForks; i++) {
        if (sizeof total == read(fd[i][0], &total, sizeof total))
            finalResult += total;
        close(fd[i][0]);
    }

    for (int i = 0; i < numForks; i++)
        wait(NULL);

    printf("The grand total: %d\n", finalResult);
}

用于生成要使用(./gen 1000 > file1.dat)进行测试的文件的代码:

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

int main(int argc, char **argv)
{
    int i = 0;

    if (argc > 1)
        i = atoi(argv[1]);

    srand((unsigned) time(NULL));

    while (i-- > 0)
        printf("%03d\n", rand() % 1000);
}

以及一个健全性检查器(./sanity-check < file1.dat):

#include <stdio.h>

int main(void)
{
    int sum = 0, num;

    while (1 == scanf("%d", &num))
        sum += num;

    printf("%d\n", sum);
}

相关问题