Как продемонстрировать непредсказуемый порядок fork () в C? - PullRequest
0 голосов
/ 20 февраля 2019

Я действительно застрял на этом задании.Это довольно простая программа, которую я должен написать на C. Идея состоит в том, чтобы продемонстрировать использование fork () и wait ().
В инструкциях говорится: «Выполните ровно 15 итераций перед завершением. На каждой итерации программа создаст 10 дочерних процессов (от p0 до p9). Каждый процесс напечатает свою десятичную цифру один раз без новой строки перед выходом. После создания 10потомки, родительский процесс будет ждать до тех пор, пока не завершится 10 потомков, затем напечатает новую строку перед переходом к следующей итерации или выходу. Не вызывайте 10 явных вызовов fork (), используйте цикл только с одним экземпляром fork ()Чтобы дождаться завершения всех дочерних элементов, используйте wait (NULL) в цикле, пока возвращаемое значение wait (NULL) не будет равно -1. "
Вот пример выходных данных:

9856724310
8149765320
2789654310
0139874265
8765320149
2145367809
0123456798
0124356789
0123546789
9854320761
0123678594
0142685379
0123456789
6795438210
2394567081



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

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

using namespace std;
int main(int argc, char** argv) {
    pid_t pid;
    for(int i=0; i<15; i++){
        //fork();
        for(int j=0; j<10; j++){
            pid = fork();
            printf("%d", j);
        }
        wait(NULL);
        printf("\n");
    }
    return 0;
}

Любая помощь с благодарностью!Спасибо!

Ответы [ 5 ]

0 голосов
/ 21 февраля 2019

Я пытаюсь это просто напечатать числа без какой-либо непредсказуемости.

Когда поток вызывает fork():

  • возможно ядроразработан таким образом, что ЦП создает новый процесс и переключается на новый процесс, и, возможно, (если имеется несколько ЦП) новый процесс должен ждать, пока другой ЦП не проснется, и проверить свою очередь планировщика, прежде чем новый процесс получит какой-либо ЦП.время
  • возможно, ядро ​​спроектировано так, что ЦП создает новый процесс и возвращает к старому процессу, и, возможно, (если имеется несколько ЦП) старый процесс должен ждать, пока другой ЦП не проснется и проверитего очередь планировщика до того, как новый процесс получит какое-либо процессорное время
  • , возможно, ядро ​​спроектировано так, чтобы ЦП выполнял как можно меньше работы, прежде чем вернуться к старому процессу, а затем (возможно, намного позже), когда новый процесс получит ЦПвремя, когда ядро ​​завершило выполнение любой работы, необходимой для создания нового процесса, прежде чем вернуться к новому процессу

Обратите внимание, что все эти вещи "непредсказуемы", если вы не знаете, на каком ядре / ОС будет работать ваша программа или если будет / не будет нескольких процессоров;но с «одним и тем же ядром / ОС на одном компьютере» все они полностью предсказуемы и не изменятся (например, ядро ​​не просто самостоятельно изменяет дизайн во время работы).

Однако:

  • возможно, старая задача использовала почти весь свой временной интервал до вызова fork(), и ядро ​​решает, что старая задача использовала весь свой временной интервал сразу после завершения fork();и это расстраивает время
  • , может быть, многие другие процессы постоянно блокируются / разблокируются;и это нарушает синхронизацию
  • , может быть, IRQ происходит в определенной точке, и это нарушает синхронизацию

Обратите внимание, что эти вещи создают «непредсказуемость» даже с «тем же ядром / ОС на том жекомпьютер ";но все они относительно редки.Возможно, вам придется fork() несколько миллионов раз, прежде чем вам повезет, и одна из этих вещей нарушает время, достаточное для изменения "кто достигнет printf() первым".

Имея это в виду;продемонстрировать «редкую непредсказуемость в одном ядре / ОС на одном компьютере»;Я, вероятно, написал бы программу, которая:

  • вызывает fork()
  • , чтобы оба процесса печатали что-то различное без символов новой строки (например, старый процесс печатает «0» и новый процессвыводит «1»)
  • вызов нового процесса exit()
  • старый процесс ожидает завершения нового процесса
  • старый процесс выводит символ новой строки (например, такчто полная напечатанная строка будет «01 \ n» или «10 \ n»)
  • , старый процесс возвращается к началу (например, цикл, который выполняется миллион раз).

Таким образом, проверив весь миллион строк вывода, вы получите относительно высокий шанс увидеть некоторую «редкую непредсказуемость».

0 голосов
/ 20 февраля 2019

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

fork может быть трудно обрести голову.При его вызове родительские процессы и продолжают выполнение кода.Из-за этого важно отделить дочерний код и убедиться, что ребенок выходит.Если родитель и дочерний элемент выполняют один и тот же код, как вы гарантируете, что дочерний элемент останавливается, а родительский продолжает?

fork в родительском элементе возвращает идентификатор процесса дочернего элемента.fork в потомке возвращает 0. И если есть ошибка, он вернет -1.Таким образом, типичная идиома выглядит следующим образом:

        pid_t pid = fork();
        if( pid == 0 ) {
            // child
            exit(0);
        }
        else if( pid < 0 ) {
            // error
            perror("fork failed");
        }

        // parent

После того, как вы закончите, вы должны дождаться завершения всех детей.wait вернется, когда любой дочерний процесс завершится, вернув дочерний pid.Вам нужно продолжать вызывать wait до тех пор, пока он не вернется с <0, указывающим на то, что дочерних процессов больше не было. </p>

void wait_all() {
    while(wait(NULL) > 0);
}

Соединение их всех вместе и удаление компиляции при выполнении 15 раз ...

    for(int j=0; j<10; j++){
        pid_t pid = fork();
        if( pid == 0 ) {
            // print
            printf("%d", j);
            // job's done
            exit(0);
        }
        else if( pid < 0 ) {
            perror("fork failed");
        }
    }

    wait_all();
    printf("\n");

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


Side Note : stdout - строка с буферизациейЭто означает, что его содержимое будет отображаться или сбрасываться только при появлении новой строки.printf("%d", j); не будет отображаться, пока не будет напечатана новая строка или stdout не будет сброшено.exit очистит и закроет все потоки, так что все в порядке.

Однако дочерний объект наследует буфер родителя.Это может привести к довольно странному поведению, если родитель оставляет что-либо в буфере.Например ...

    for(int j=0; j<10; j++){
        pid_t pid = fork();
        if( pid == 0 ) {
            printf("child: %d, ", j);
            exit(0);
        }
        else if( pid < 0 ) {
            perror("fork failed");
        }
        printf("parent: %d, ", j);
    }
    wait_all();
    printf("\n");

Мы получаем:

child: 0, parent: 0, child: 1, parent: 0, parent: 1, child: 2, parent: 0, parent: 1, parent: 2, child: 3, parent: 0, parent: 1, parent: 2, parent: 3, child: 4, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, child: 5, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, child: 6, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, child: 7, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, child: 8, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, parent: 8, child: 9, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, parent: 8, parent: 9,

Что происходит?Это ошеломило меня на некоторое время.Немного понятнее, если мы поместим новую строку в печать ребенка.

    for(int j=0; j<10; j++){
        pid_t pid = fork();
        if( pid == 0 ) {
            printf("child: %d\n", j);
            exit(0);
        }
        else if( pid < 0 ) {
            perror("fork failed");
        }
        printf("parent: %d, ", j);
    }
    wait_all();
    printf("\n");

child: 0
parent: 0, child: 1
parent: 0, parent: 1, child: 2
parent: 0, parent: 1, parent: 2, child: 3
parent: 0, parent: 1, parent: 2, parent: 3, child: 4
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, child: 5
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, child: 6
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, child: 7
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, child: 8
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, parent: 8, child: 9
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, parent: 8, parent: 9, 

Каждый раз, когда ребенок разветвляется, он получает копию буфера stdout родителя.Каждый раз в цикле родитель добавляет parent: %d, в этот буфер без очистки.Когда дочерний элемент делает printf("child: %d\n", j);, он добавляет к существующему буферу и затем сбрасывает его.

  • Первый дочерний элемент копирует "" из родительского элемента, добавляет child: 0\n исбрасывает.
  • Родитель добавляет parent: 0, к stdout.
  • Второй дочерний элемент копирует parent: 0,, добавляет child: 1\n и сбрасывает.
  • Родитель добавляетparent: 1, до stdout, теперь его parent: 0, parent: 1,
  • Третий дочерний элемент копирует parent: 0, parent: 1,, добавляет child: 2\n и сбрасывает.

И т. Д.

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

    for(int j=0; j<10; j++){
        pid_t pid = fork();
        if( pid == 0 ) {
            printf("child: %d\n", j);
            exit(0);
        }
        else if( pid < 0 ) {
            perror("fork failed");
        }
        printf("parent: %d, ", j);
        fflush(stdout);
    }
    wait_all();
    printf("\n");

parent: 0, child: 0
parent: 1, child: 1
parent: 2, child: 2
parent: 3, child: 3
parent: 4, child: 4
parent: 5, child: 5
parent: 6, child: 6
parent: 7, child: 7
parent: 8, child: 8
parent: 9, child: 9
0 голосов
/ 20 февраля 2019

Вы должны различать код, который будет запускаться в родительском или дочернем процессе (теперь вы выполняете один и тот же код в обоих процессах).Это можно сделать с помощью возвращаемого значения fork (), поскольку fork возвращает pid дочернего элемента родительскому элементу и возвращает 0 дочернему элементу.Следовательно, вы можете выполнить проверку как:

pid = fork();
if (pid == 0) {
    //Code for child process
    exit(0);
}
//Code for parent process
0 голосов
/ 20 февраля 2019

Когда вы используете дочерние процессы, всегда различайте родительский код и дочерний код.Дочерний объект идентифицируется по pid=0, в то время как у родительского процесса будет pid > 0 в соответствии с тем, что pid имеет во время выполнения.Также, если во время разветвления дочернего процесса произойдет ошибка, fork вернет значение <0, поэтому вы должны рассмотреть этот случай.

В вашем коде такого нет.Классический способ сделать это - сделать что-то вроде этого:

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

int main(void) {
    pid_t pid;
    for(int i=0;i<15;i++){
        for(int j=0; j<10; j++){
            if((pid = fork())<0){
               perror("Fork Failed.");
               exit(EXIT_FAILURE);
            }
            if(pid==0){ /*Child Code here*/
               printf("%d", j);
               exit(EXIT_SUCCESS);
            }
        }
        while(wait(NULL)!=-1); //while loop to wait
        printf("\n");
    }

    return 0;
}
0 голосов
/ 20 февраля 2019

Вы поняли это неправильно.В fork () вы можете представить, что код из fork () и далее выполняется одновременно, причем оба процесса используют один и тот же код (поскольку копируется память).Различить parent и child можно с помощью предложения if, поскольку fork () возвращает разные значения для каждого процесса.Как показано ниже:

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

int main(void) {
    for (int i=0; i<15; i++){
        for(int j=0; j<10; j++){
            if (fork()==0){
                //child process return 0 from fork()
                printf("%d", j);
                exit(0);
            }
            else{
                //parent process return pid of the forked process from fork()
                continue;
            }
        }
        while (wait(NULL)!=-1);//wait for all child processes. wait() only return -1 when no more children to wait for
        printf("\n");
    }
    return 0;
}
...