it-swarm.com.de

Warum druckt ein Programm mit fork () seine Ausgabe manchmal mehrmals?

In Programm 1 wird Hello world Nur einmal gedruckt, aber wenn ich \n Entferne und ausführe (Programm 2), wird die Ausgabe 8 Mal gedruckt. Kann mir bitte jemand erklären, welche Bedeutung \n Hier hat und wie sich dies auf die fork() auswirkt?

Programm 1

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

int main()
{
    printf("hello world...\n");
    fork();
    fork();
    fork();
}

Ausgabe 1:

hello world... 

Programm 2

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

int main()
{
    printf("hello world...");
    fork();
    fork();
    fork();
}

Ausgabe 2:

hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...
51
lmaololrofl

Bei der Ausgabe auf die Standardausgabe mit der Funktion printf() der C-Bibliothek wird die Ausgabe normalerweise gepuffert. Der Puffer wird erst geleert, wenn Sie eine neue Zeile ausgeben, fflush(stdout) aufrufen oder das Programm beenden (allerdings nicht durch Aufrufen von _exit()). Der Standardausgabestream wird auf diese Weise standardmäßig zeilengepuffert, wenn er mit einem TTY verbunden ist.

Wenn Sie den Prozess in "Programm 2" verzweigen, erben die untergeordneten Prozesse jeden Teil des übergeordneten Prozesses, einschließlich des nicht leeren Ausgabepuffers. Dadurch wird der nicht geleerte Puffer effektiv in jeden untergeordneten Prozess kopiert.

Wenn der Prozess beendet ist, werden die Puffer geleert. Sie starten insgesamt acht Prozesse (einschließlich des ursprünglichen Prozesses), und der nicht geleerte Puffer wird am Ende jedes einzelnen Prozesses geleert.

Es ist acht, weil Sie bei jeder fork() die doppelte Anzahl von Prozessen erhalten, die Sie vor der fork() hatten (da sie bedingungslos sind), und Sie haben drei davon (23 = 8).

94
Kusalananda

Die Gabel wird dadurch in keiner Weise beeinflusst.

Im ersten Fall haben Sie 8 Prozesse, bei denen nichts zu schreiben ist, da der Ausgabepuffer bereits geleert wurde (aufgrund des \n).

Im zweiten Fall haben Sie noch 8 Prozesse, jeder mit einem Puffer, der "Hallo Welt ..." enthält, und der Puffer wird am Prozessende geschrieben.

18
edc65

@Kusalananda erklärte, warum die Ausgabe wiederholt ist. Wenn Sie neugierig sind, warum die Ausgabe wiederholt wird 8 Mal und nicht nur 4 Mal (das Basisprogramm + 3 Gabeln):

int main()
{
    printf("hello world...");
    fork(); // here it creates a copy of itself --> 2 instances
    fork(); // each of the 2 instances creates another copy of itself --> 4 instances
    fork(); // each of the 4 instances creates another copy of itself --> 8 instances
}
12
Honza Zidek

Der wichtige Hintergrund hierbei ist, dass stdout standardmäßig zeilengepuffert sein muss.

Dies bewirkt, dass ein \n Die Ausgabe leert.

Da das zweite Beispiel keinen Zeilenumbruch enthält, wird die Ausgabe nicht gelöscht, und da fork() den gesamten Prozess kopiert, wird auch der Status des Puffers stdout kopiert.

Mit diesen fork() -Aufrufen in Ihrem Beispiel werden nun insgesamt 8 Prozesse erstellt - alle mit einer Kopie des Status des stdout -Puffers.

Per Definition rufen alle diese Prozesse exit() auf, wenn sie von main() zurückkehren, und exit() ruft fflush() auf, gefolgt von fclose() on alle aktiven stdio Streams. Dies beinhaltet stdout und als Ergebnis sehen Sie den gleichen Inhalt achtmal.

Es wird empfohlen, fflush() für alle Streams mit ausstehender Ausgabe aufzurufen, bevor fork() aufgerufen wird, oder das gegabelte untergeordnete Element explizit _exit() aufrufen zu lassen, das den Prozess nur ohne Leeren beendet die stdio-Streams.

Beachten Sie, dass der Aufruf von exec() die stdio-Puffer nicht leert. Es ist daher in Ordnung, sich nicht um die stdio-Puffer zu kümmern, wenn Sie (nach dem Aufruf von fork()) exec() und aufrufen (Wenn dies fehlschlägt) Rufen Sie _exit() auf.

Übrigens: Um zu verstehen, dass eine falsche Pufferung dazu führen kann, ist hier ein früherer Fehler in Linux, der kürzlich behoben wurde:

Der Standard verlangt, dass stderr standardmäßig ungepuffert ist, aber Linux hat dies ignoriert und die Zeile stderr gepuffert und (noch schlimmer) vollständig gepuffert, falls stderr über eine Pipe umgeleitet wurde. Für UNIX geschriebene Programme haben also unter Linux zu spät Sachen ohne Zeilenumbruch ausgegeben.

Siehe Kommentar unten, es scheint jetzt behoben zu sein.

Dies ist, was ich tue, um dieses Linux-Problem zu umgehen:

    /* 
     * Linux comes with a broken libc that makes "stderr" buffered even 
     * though POSIX requires "stderr" to be never "fully buffered". 
     * As a result, we would get garbled output once our fork()d child 
     * calls exit(). We work around the Linux bug by calling fflush() 
     * before fork()ing. 
     */ 
    fflush(stderr); 

Dieser Code schadet auf anderen Plattformen nicht, da das Aufrufen von fflush() in einem Stream, der gerade gelöscht wurde, ein Noop ist.

4
schily