Почему один процесс не достигает выхода (0) при разветвлении?

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

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

#include <sys/shm.h>
#include <sys/ipc.h>

int main() {
    
    key_t key = ftok(".", 'x');
    int shmid = shmget(key, sizeof(int)*4, IPC_CREAT | 0666);
    if (shmid < 0) {
        perror("shmget\n");
        return 1;
    }
    int *arr = shmat(shmid, NULL, 0);
    pid_t c1 = fork();
    if (c1==0) {
        pid_t c2 = fork();
        if (c2==0) {
            pid_t c3 = fork();
            if (c3==0) {
                arr[0] = 10;
            } else {
                arr[1] = 11;
            }
            exit(0);
        } else {
            arr[2] = 12;
        }
        exit(0);
    } else {
        arr[3] = 13;
        wait(NULL);
        wait(NULL);
        wait(NULL);

        for (int i=0; i<4; i++) printf("%d ", arr[i]);
        printf("\n");
    }
    exit(0);
}

Это печатает 0 11 12 13, показывая, что по какой-то причине значение 10 никогда не присваивается arr[0].

Я ожидал, что каждый процесс дойдет до какого-то вызова exit(0). Я думаю, особенно тот, что после третьего разветвления, должен достичь того же вызова exit(0), который достигается вторым разветвлением.

Однако когда я добавляю явный вызов выхода внутри третьего блока разветвления (т. е. после c3==0), он выполняется так, как ожидалось.

Когда я пишу это, мне вдруг приходит в голову догадка о том, почему это происходит: не потому ли, что второй дочерний элемент достигает выхода раньше третьего, и поскольку он выходит, то связь от четвертого до родителя разрывается?

Вам следует добавить четвертый wait(NULL);. Или еще лучше: while (wait(NULL) >= 0);

Craig Estey 05.08.2024 18:29

вы можете только wait() для своих детей. Вызов wait(NULL) 3 раза в исходном родительском процессе не будет ждать внуков.

Barmar 05.08.2024 18:29

Проверьте, что возвращают эти вызовы wait. Могу поспорить, что хотя бы один из них потерпит неудачу (поскольку вы не запускаете все дочерние процессы в первом, исходном, родительском процессе).

Some programmer dude 05.08.2024 18:30

@Бармар, мне это как бы интересно. Но по всему Интернету я вижу такие вещи, как то, что пишет Крейг Эсти выше, которое, похоже, предполагает, что вы можете неоднократно вызывать ожидание в одном и том же месте. В любом случае я переместил ожидания в другие места и теперь получаю загадочную ошибку недопустимого аргумента для shmget. Кажется, это как-то связано с использованием уже выделенной памяти, но я не знаю почему. В любом случае, скорее всего, это не связано с вышеупомянутой проблемой, поэтому мне, вероятно, придется разобраться в этом где-то еще.

Addem 05.08.2024 18:46

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

Some programmer dude 05.08.2024 18:49

@Someprogrammerdude Да, это имеет смысл - но я видел, как предположил Крейг, делая это, даже когда есть внуки. Я почти уверен, что по крайней мере на одной веб-странице и в одном видео на YouTube я видел, как люди писали это. Я не уверен, это просто неправильно или происходит что-то еще, чего я не понимаю.

Addem 05.08.2024 18:51

Либо они тоже не правы, либо вы неправильно прочитали. Может оказаться затруднительным определить, создает ли родительский элемент несколько собственных дочерних элементов или они создаются дочерними элементами.

Barmar 05.08.2024 18:53

Попробуйте добавить код в каждый процесс, чтобы вывести getpid() и getppid(), чтобы увидеть взаимосвязи.

Barmar 05.08.2024 18:55

В Linux вы можете использовать prctl() из <sys/prctl.h> вместе с PR_SET_CHILD_SUBREAPER (см. prctl(2)), чтобы текущий процесс извлекал всех своих потомков (вместо того, чтобы позволить системному процессу собирать своих косвенных потомков).

Jonathan Leffler 06.08.2024 00:58

К сожалению, на каждое хорошее видео или урок приходится десять не столь хороших. Найти хорошие непросто.

Some programmer dude 06.08.2024 04:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
10
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

Чтобы гарантировать, что все дети закончили работу, каждый родитель может дождаться своего ребенка.

Пример:

pid_t c1 = fork();
if (c1==0) { // child
    pid_t c2 = fork();
    if (c2==0) { // child
        pid_t c3 = fork();
        if (c3==0) { // child
            arr[0] = 10;
            exit(0);
        } else {
            arr[1] = 11;
            waitpid(c3, NULL, 0); // waiting for child
        }
        exit(0);
    } else {
        arr[2] = 12;
        waitpid(c2, NULL, 0);    // waiting for child
    }
    exit(0);
} else {
    arr[3] = 13;
    waitpid(c1, NULL, 0);    // waiting for child
    for (int i=0; i<4; i++) printf("%d ", arr[i]);
    printf("\n");
}

@zwol Тебе, должно быть, очень нравится симметрия. Мне нравится, как вы внесли изменения в мой ответ, и теперь я думаю: «Стоит ли мне удалить все exit(0)», раз уж вы только что сделали их ненужными? :-) Хотелось бы, чтобы была кнопка +1 для редактирования.

Ted Lyngmo 06.08.2024 02:09

Все, о чем я думал, это то, что «этот код был бы менее запутанным для новичков, если бы он явно вызывал waitpid только на родительской стороне каждого форка»…

zwol 06.08.2024 04:51

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