Visuell, was mit fork () in einer For-Schleife passiert

74

Ich habe versucht, fork()Verhalten zu verstehen . Diesmal in einem for-loop. Beachten Sie den folgenden Code:

#include <stdio.h>

void main()
{
   int i;

   for (i=0;i<3;i++)
   {
      fork();

      // This printf statement is for debugging purposes
      // getppid(): gets the parent process-id
      // getpid(): get child process-id

      printf("[%d] [%d] i=%d\n", getppid(), getpid(), i);
   }

   printf("[%d] [%d] hi\n", getppid(), getpid());
}

Hier ist die Ausgabe:

[6909][6936] i=0
[6909][6936] i=1
[6936][6938] i=1
[6909][6936] i=2
[6909][6936] hi
[6936][6938] i=2
[6936][6938] hi
[6938][6940] i=2
[6938][6940] hi
[1][6937] i=0
[1][6939] i=2
[1][6939] hi
[1][6937] i=1
[6937][6941] i=1
[1][6937] i=2
[1][6937] hi
[6937][6941] i=2
[6937][6941] hi
[6937][6942] i=2
[6937][6942] hi
[1][6943] i=2
[1][6943] hi

Ich bin eine sehr visuelle Person, und so kann ich die Dinge nur durch Diagramme wirklich verstehen. Mein Lehrer sagte, es würde 8 Hi- Statements geben. Ich habe den Code geschrieben und ausgeführt, und tatsächlich gab es 8 Hi- Anweisungen. Aber ich habe es wirklich nicht verstanden. Also habe ich folgendes Diagramm gezeichnet:

Geben Sie hier die Bildbeschreibung ein

Diagramm aktualisiert, um Kommentare wiederzugeben :)

Beobachtungen:

  1. Der übergeordnete Prozess (main) muss die Schleife dreimal durchlaufen. Dann wird printf aufgerufen
  2. Bei jeder Iteration der übergeordneten for-Schleife wird ein fork () aufgerufen
  3. Nach jedem Aufruf von fork () wird i inkrementiert, und so startet jedes Kind eine for-Schleife von i, bevor es inkrementiert wird
  4. Am Ende jeder for-Schleife wird "hi" gedruckt

Hier sind meine Fragen:

  • Ist mein Diagramm korrekt?
  • Warum gibt es zwei Instanzen i=0in der Ausgabe?
  • Welcher Wert von iwird nach der Gabelung () auf jedes Kind übertragen? Wenn der gleiche Wert von übertragen iwird, wann hört dann das "Gabeln" auf?
  • Ist es immer so, dass 2^n - 1die Anzahl der gegabelten Kinder gezählt werden kann? Also, hier n=3, was bedeutet 2^3 - 1 = 8 - 1 = 7Kinder, was ist richtig?
lucidgold
quelle
Warum nicht ausführen und idie PID und die übergeordnete PID nach dem ausdrucken fork(). Es sollte einfach sein zu verfolgen, was passiert
Basic
3
@Basic, das ist das erste was ich getan habe. Ich habe sogar getpid () und getppid () verwendet und deshalb glaube ich, dass mein Diagramm korrekt ist. Aber ich möchte wirklich, dass jemand dies überprüft.
Lucidgold
Das ist ein wirklich schönes Diagramm. Hast du es mit dot / graphviz geschafft?
Steven Lu
1
Ich habe Microsoft Visio verwendet. Aber ich benutze jetzt LibreOffice Draw, sehr ähnlich zu Open Office Draw und beide sind Open Source-Projekte, glaube ich, also kostenlos!
Lucggold
1
Wie haben Sie die Pufferung standardmäßig deaktiviert? Auf der gfg ide sind die Ausgaben ohne fflush - ide.geeksforgeeks.org/0TWiEZ und mit fflush - ide.geeksforgeeks.org/0JKaH5
Udayraj Deshmukh

Antworten:

43

Hier erfahren Sie, wie Sie es verstehen, beginnend mit der forSchleife.

  1. Schleife beginnt im übergeordneten, i == 0

  2. Elternteil fork() s, Kind erstellen 1.

  3. Sie haben jetzt zwei Prozesse. Beide drucken i=0.

  4. Die Schleife wird jetzt in beiden Prozessen neu gestartet i == 1.

  5. Eltern und Kind 1 fork(), Kinder 2 und 3 erstellen.

  6. Sie haben jetzt vier Prozesse. Alle vier drucken i=1.

  7. Die Schleife wird jetzt in allen vier Prozessen neu gestartet i == 2.

  8. Eltern und Kinder 1 bis 3, alle fork()Kinder 4 bis 7.

  9. Sie haben jetzt acht Prozesse. Alle acht drucken i=2.

  10. Die Schleife wird jetzt in allen acht Prozessen neu gestartet i == 3.

  11. Die Schleife wird in allen acht Prozessen beendet, da dies i < 3nicht mehr der Fall ist.

  12. Alle acht Prozesse werden gedruckt hi.

  13. Alle acht Prozesse werden beendet.

Sie werden also 0zweimal 1gedruckt, viermal 2gedruckt, achtmal higedruckt und achtmal gedruckt.

Crowman
quelle
1
Genial, also ist mein Diagramm korrekt. Dies erklärt jedoch, warum es zwei Instanzen von i = 0 gibt (weil ich immer den aktuellen Wert von i drucke). Obwohl i = 0 übertragen wird, wird die nächste Gabel nur ausgeführt, wenn i inkrementiert wird! Vielen Dank!
Lucidgold
4
Ja, fork()dupliziert nur den Prozess und beide machen auf die gleiche Weise weiter. Beide sind gerade von einem fork()Anruf zurückgekehrt und werden erst wieder anrufen, wenn sie das nächste Mal auf einen Anruf bei stoßen fork(). Der einzige Unterschied besteht darin, dass fork()beim Kind 0 und beim Elternteil etwas anderes zurückgegeben wurde. Bei jedem Prozess sind beide nur von a zurückgekehrt fork()und fahren dann fort.
Crowman
@PaulGriffiths Nach Ihrer Erklärung wird es ein ausgeglichener Baum sein. ist es nicht so, aber die Reihenfolge der Ausführung würde variieren, da die Reihenfolge nicht deterministisch ist, sondern im Wesentlichen ausgewogen. Ist es nicht?
Naseer
@khan: Meine Erklärung spricht nicht dafür, wann eines dieser Dinge passieren wird, wie oft sie passieren werden. Sie haben Recht, dass die Ausführungsreihenfolge im Allgemeinen nicht vorhersehbar ist.
Crowman
1
@ Khan: Ich bin mir nicht sicher, wie ich mehr als die Antwort ausarbeiten soll. Zieh es einfach heraus. Beginnen Sie mit einem Knoten und fügen Sie dann dreimal jedem untergeordneten Knoten ein untergeordnetes Element hinzu. Sie erhalten acht Knoten: einen mit drei Kindern, einen mit zwei, zwei mit einem und vier mit keinem.
Crowman
12
  1. Ja das ist korrekt. (siehe unten)
  2. Nein, i++wird nach dem Aufruf von ausgeführt fork, denn so ist dasfor Schleife so funktioniert.
  3. Wenn alles erfolgreich geht, ja. Denken Sie jedoch daran, dass dies forkmöglicherweise fehlschlägt.

Eine kleine Erklärung zum zweiten:

for (i = 0;i < 3; i++)
{
   fork();
}

ist ähnlich wie:

i = 0;
while (i < 3)
{
    fork();
    i++;
}

So iin den gegabelten Prozesse (beide Eltern und Kind) ist der Wert vor dem Zuwachs. Das Inkrement wird jedoch unmittelbar danach ausgeführt fork(), sodass das Diagramm meiner Meinung nach als korrekt behandelt werden kann.

Yu Hao
quelle
Also, da ich nach dem Aufruf von fork () inkrementiert werde, ist i in einem Kind der letzte Wert von i vom Elternteil? Also ist mein Diagramm NICHT korrekt?
Lucidgold
@lucidgold Deshalb habe ich dir gesagt, du sollst einen printf einfügen.
2501
1
@lucidgold Ich denke, Ihr Diagramm ist korrekt, das Gabeln stoppt, weil ich unmittelbar nach dem Anruf inkrementiert bin. Yu Hao erklärte es gut.
2501
1
@ Lucidgold: Weil du printf() nach dem fork(). iwird 0sowohl im Elternteil als auch im ersten Kind sein, und beide werden diesen printf()Aufruf ausführen . Was Sie in Ihrem Diagramm vermissen, ist, dass das erste rote Kind eine haben sollte i == 0, aber diese Box wird es nicht fork(). Das gleiche gilt für das zweite rote Kind, es sollte eine i == 1Box für das haben printf(), aber diese Box wird es auch nicht fork().
Crowman
1
@lucidgold: Zum Zeitpunkt der Erstellung des ersten Kindes ist das erste fork()bereits passiert. Das erste Kind wird fork()zum ersten Mal bei der nächsten Iteration der Schleife, wenn i == 1. Aber es wird die erste Iteration der Schleife abschließen, bevor es das tut (weil es in der Mitte dieser Iteration erstellt wurde), also wird es printf()die i=0.
Crowman
4

Um Ihre Fragen einzeln zu beantworten:

Ist mein Diagramm korrekt?

Ja, im Wesentlichen. Es ist auch ein sehr schönes Diagramm.

Das heißt, es ist richtig, wenn Sie die i=0Beschriftungen usw. so interpretieren, dass sie sich auf Iterationen mit vollständiger Schleife beziehen. Was das Diagramm jedoch nicht zeigt, ist, dass nach jedem fork()der Teil der aktuellen Schleifeniteration nach dem fork()Aufruf auch vom gegabelten untergeordneten Prozess ausgeführt wird.

Warum gibt es zwei Instanzen i=0in der Ausgabe?

Weil du die hast printf()nach dem fork(), so ist es sowohl von dem Eltern - Prozess und der gerade abgezweigten Kind Prozess ausgeführt. Wenn Sie das printf()vor dem verschieben fork(), wird es nur vom übergeordneten Prozess ausgeführt (da der untergeordnete Prozess noch nicht vorhanden ist).

Welcher Wert von iwird nach dem auf jedes Kind übertragen fork()? Wenn der gleiche Wert von übertragen iwird, wann hört dann das "Gabeln" auf?

Der Wert von iwird von nicht geändertfork() , sodass der untergeordnete Prozess denselben Wert wie sein übergeordneter Prozess sieht.

Die Sache, an die man sich erinnern sollte fork() dass es einmal aufgerufen wird, aber zweimal zurückgegeben wird - einmal im übergeordneten Prozess und einmal im neu geklonten untergeordneten Prozess.

Betrachten Sie für ein einfacheres Beispiel den folgenden Code:

printf("This will be printed once.\n");
fork();
printf("This will be printed twice.\n");
fork();
printf("This will be printed four times.\n");
fork();
printf("This will be printed eight times.\n");

Der von erstellte untergeordnete Prozess fork()ist ein (fast) exakter Klon seines übergeordneten Prozesses. Aus seiner eigenen Sicht "erinnert" er sich daran, sein übergeordneter Prozess zu sein, und erbt den gesamten Status des übergeordneten Prozesses (einschließlich aller Variablenwerte, des Aufrufstapels und des Anweisung wird ausgeführt). Der einzige unmittelbare Unterschied (abgesehen von Systemmetadaten wie der von zurückgegebenen Prozess-ID getpid()) ist der Rückgabewert von fork(), der im untergeordneten Prozess Null, im übergeordneten Prozess jedoch ungleich Null (tatsächlich die ID des untergeordneten Prozesses) ist.

Ist es immer so, dass 2^n - 1die Anzahl der gegabelten Kinder gezählt werden kann? Also, hier n=3, was bedeutet 2^3 - 1 = 8 - 1 = 7Kinder, was ist richtig?

Jeder Prozess, der a ausführt, fork()wird zu zwei Prozessen (außer unter ungewöhnlichen Fehlerbedingungen, bei denen ein fork()Fehler auftreten kann). Wenn Eltern und Kind weiterhin denselben Code ausführen (dh sie überprüfen nicht den Rückgabewert von fork()oder ihre eigene Prozess-ID und verzweigen darauf basierend auf verschiedene Codepfade), verdoppelt jede nachfolgende Verzweigung die Anzahl der Prozesse. Ja, nach drei Gabeln erhalten Sie insgesamt 2³ = 8 Prozesse.

Ilmari Karonen
quelle
Hervorragende Antwort. Ich habe mein Diagramm aktualisiert, um Ihre Eingabe widerzuspiegeln. Vielen Dank!
Lucidgold