Почему эта многопоточная программа не может записать во второй файл?

Я пытаюсь написать программу, которая получает на вход до 10 .txt файлов, содержащих строки с именами учеников и их оценками. Предполагается, что программа создает новый процесс для каждого входного файла и записывает имя каждого учащегося и среднюю оценку во временный файл, названный в честь pid процесса.

В конце концов, родительский процесс должен дождаться завершения всех процессов и создать новый процесс для объединения всех временных файлов в комбинированный выходной файл. Я изо всех сил пытался правильно передать pids вплоть до создания выходного файла, поэтому я всегда успешно записываю только один файл, а другой нет (при работе с двумя входными файлами).

По сути, каждый раз отсутствует один из pid, поэтому моя программа пытается прочитать несуществующий файл. Я изо всех сил пытаюсь понять, почему это происходит, и возможное исправление. Любая помощь будет оценена.

Программа:

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

#define GRADES_FILE "all_std.log"

typedef struct {
    char name[11];
    int grades[256];
    int num_grades;
} student_t;

void report_data_summary(int num_stud) {
    fprintf(stderr, "grade calculation for %d students is done\n", num_stud);
}

void calculate_students_grade_average_from_input(int file_number, char* filenames[])
{
    student_t students[256];
    int num_students = 0;
    char filename[50];
    sprintf(filename, "%d.temp", getpid());

    FILE* fp_in = fopen(filenames[file_number], "r");
    if (fp_in == NULL) {
        printf("Could not open file %s\n", filenames[file_number]);
        exit(EXIT_FAILURE);
    }

    while (!feof(fp_in)) {
        fscanf(fp_in, "%s", students[num_students].name);
        int grade;
        students[num_students].num_grades = 0;
        while (fscanf(fp_in, "%d", &grade) == 1) {
            students[num_students].grades[students[num_students].num_grades] = grade;
            students[num_students].num_grades++;
        }
        num_students++;
    }
    fclose(fp_in);

    FILE* fp_out = fopen(filename, "w");
    if (fp_out == NULL) {
        printf("Could not open output file %s\n", filename);
        return;
    }

    for (int j = 0; j < num_students; ++j) {
        int sum = 0;
        for (int k = 0; k < students[j].num_grades; ++k) {
            sum += students[j].grades[k];
        }
        float avg = (float)sum / students[j].num_grades;
        fprintf(fp_out, "%s %.1f\n", students[j].name, avg);
    }
    fclose(fp_out);

    fprintf(stderr, "process: %d file: %s number of students: %d\n", getpid(), filenames[file_number], num_students);
}


void create_output(int file_count, pid_t* temp_processes[])
{
    int total_students = 0;
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    }
    else if (pid == 0) {
        FILE* fp_final = fopen(GRADES_FILE, "w");
        if (fp_final == NULL) {
            perror("Could not open final output file");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < file_count; ++i) {
            char temp_filename[16];
            sprintf(temp_filename, "%d.temp", temp_processes[i]);
            
            FILE* fp_temp = fopen(temp_filename, "r");
            if (fp_temp == NULL) {
                printf("Could not open temporary file %s\n",temp_filename);
                return;
            }

            char line[101];
            while (fgets(line, sizeof(line), fp_temp)) {
                fputs(line, fp_final);
                total_students++;
            }

            report_data_summary(total_students);
            fclose(fp_temp);
        }

        fclose(fp_final);
        return;
    }
    else {
        wait(NULL);
    }
}




void ex01(int file_count, char* filenames[])
{
    pid_t temp_processes[10];
    pid_t pid;

    for (int i = 1; i < file_count; ++i) {
        pid = fork();

        if (pid < 0) {
            perror("fork failed");
            exit(EXIT_FAILURE);
        }
        else if (pid > 0) {
            temp_processes[i-1] = pid;
            printf("%d = %d\n",0,temp_processes[0]);
            printf("%d = %d\nend\n",1,temp_processes[1]);
        }
        else {
            calculate_students_grade_average_from_input(i, filenames);
            return;
        }
    }

    for (int i = 1; i < file_count; ++i) {
        wait(NULL);
    }
    

    if (pid > 0)
    {
        create_output(file_count - 1, temp_processes);
    }
}

int main(int argc, char* argv[]) {
    ex01(argc, argv);
    return 0;
}

Вход:

file1.txt:
Abraham 80 90 75
Benny 90
Garland 70 9   90 100

file2.txt:
Dana 90 95
Ron 100 80 90

Выходной файл:

Abraham 81.7
Benny 90.0
Garland 67.2

В выводе отсутствуют студенты второго файла.

Вывод консоли:

0 = 35222
1 = 0
end
0 = 35222
1 = 35223
end
process: 35223 file: gr_2.txt number of students: 2
process: 35222 file: gr_1.txt number of students: 3
grade calculation for 3 students is done
Could not open temporary file 0.temp

Функция fork создает совершенно новый и независимый процесс, а не поток. Также помните, что процессы действительно независимы друг от друга, со своей собственной картой виртуальной памяти. Вы ожидаете, что между процессами нет общей памяти.

Some programmer dude 23.07.2023 18:34

Я знаю, я пытаюсь преодолеть этот пробел, используя временные файлы и сохраняя их имена, чтобы в конечном итоге использовать их в основном процессе. Но эта доставка имени где-то терпит неудачу, и я не могу ее найти.

SiiilverSurfer 23.07.2023 18:37

Строка while (!feof(fp_in)) неверна. Я предлагаю вам прочитать это: Почему «пока( !feof(файл) )» всегда неправильно?

Andreas Wenzel 23.07.2023 18:48

Я предлагаю вам заняться отладкой резиновой уточки вашего кода. Все процессы используют для вывода один единственный файл, который не используется совместно процессами. Когда один процесс открывает его для записи, текущее содержимое файла будет потеряно.

Some programmer dude 23.07.2023 18:49

@Someprogrammerdude Я понимаю это, но думаю, что проблема возникает заранее. Запись в файл происходит только в create_output, но сначала он пытается прочитать из несуществующего файла (неправильное имя файла), и здесь происходит сбой. Механизм передачи pids неверен.

SiiilverSurfer 23.07.2023 19:16

В строке sprintf(filename, "%d.temp", getpid()); вы, кажется, предполагаете, что возвращаемый тип getpid() (то есть pid_t) эквивалентен int. Это предположение кажется мне опасным. Я считаю, что sprintf(filename, "%jd.temp", (intmax_t)getpid()); будет безопаснее. Обратите внимание, что intmax_t требует #include <stdint.h>.

Andreas Wenzel 23.07.2023 19:31

Вывод показывает, что имя файла, который не удалось открыть, было 0.temp, но ваши временные файлы названы с помощью идентификаторов, а не индексов. 0 не является допустимым pid.

John Bollinger 23.07.2023 19:39

Я предполагаю, что вы просто экспериментируете с fork() — это интересная тема, — но ваш вариант использования здесь патологически не подходит для fork(). Вы создаете подпроцесс для чтения каждого отдельного файла... а затем последовательно читаете каждый из выходных файлов подпроцессов основной программой? Это просто не очень хороший вариант использования многопроцессорной обработки.

erik258 23.07.2023 21:53

Примечание: я предлагаю вам изменить ex01(argc, argv); на ex01(argc-1,argv+1);. Таким образом, file_count и filenames будут иметь одинаковое значение во всех функциях. Например, вы можете использовать один и тот же цикл for (int i = 0; i < file_count; ++i) { в функциях create_output и ex01 вместо использования for (int i = 1; i < file_count; ++i) { в одной функции и for (int i = 0; i < file_count; ++i) { в другой функции.

Andreas Wenzel 23.07.2023 23:59

Это не многопоточная программа. Пожалуйста, исправьте заголовок, теги и ожидания.

user207421 24.07.2023 03:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В линии

void create_output(int file_count, pid_t* temp_processes[])

вы определили второй аргумент функции как тип pid_t **. Однако в функции ex01 вы вызываете функцию create_output следующим образом:

create_output(file_count - 1, temp_processes);

Выражение temp_processes будет распадаться на &temp_processes[0], которое имеет тип pid_t*. Следовательно, ваша программа вызывает неопределенное поведение, поскольку типы второго параметра не совпадают.

Ваш компилятор должен предупредить вас об этом. Это предупреждение, которое я получаю от gcc:

<source>: In function 'ex01':
<source>:142:39: warning: passing argument 2 of 'create_output' from incompatible pointer type [-Wincompatible-pointer-types]
  142 |         create_output(file_count - 1, temp_processes);
      |                                       ^~~~~~~~~~~~~~
      |                                       |
      |                                       pid_t * {aka int *}
<source>:65:43: note: expected 'pid_t **' {aka 'int **'} but argument is of type 'pid_t *' {aka 'int *'}
   65 | void create_output(int file_count, pid_t* temp_processes[])
      |                                    ~~~~~~~^~~~~~~~~~~~~~~~

Я предлагаю вам изменить строку

void create_output(int file_count, pid_t* temp_processes[])

к:

void create_output(int file_count, pid_t temp_processes[])

Я только что попробовал это, прежде чем пришел сюда, чтобы увидеть, что вы предлагаете то же самое, и, конечно же, теперь программа работает так, как ожидалось. Большое спасибо!

SiiilverSurfer 24.07.2023 11:41

Другие вопросы по теме