web-dev-qa-db-fra.com

Pourquoi un programme avec fork () imprime-t-il parfois sa sortie plusieurs fois?

Dans le programme 1, Hello world Est imprimé une seule fois, mais lorsque je supprime \n Et l'exécute (programme 2), la sortie est imprimée 8 fois. Quelqu'un peut-il m'expliquer la signification de \n Ici et comment cela affecte la fork()?

Programme 1

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

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

Sortie 1:

hello world... 

Programme 2

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

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

Sortie 2:

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

Lors de la sortie vers une sortie standard à l'aide de la fonction printf() de la bibliothèque C, la sortie est généralement mise en mémoire tampon. Le tampon n'est pas vidé jusqu'à ce que vous sortiez une nouvelle ligne, appeliez fflush(stdout) ou quittez le programme (pas en appelant _exit() cependant). Le flux de sortie standard est par défaut mis en mémoire tampon de ligne de cette manière lorsqu'il est connecté à un TTY.

Lorsque vous bifurquez le processus dans "Programme 2", les processus enfants héritent de chaque partie du processus parent, y compris le tampon de sortie non vidé. Cela copie efficacement le tampon non vidé dans chaque processus enfant.

À la fin du processus, les tampons sont vidés. Vous démarrez un grand total de huit processus (y compris le processus d'origine) et le tampon non vidé sera vidé à la fin de chaque processus individuel.

C'est huit parce qu'à chaque fork() vous obtenez deux fois le nombre de processus que vous aviez avant la fork() (car ils sont inconditionnels), et vous en avez trois (23 = 8).

94
Kusalananda

Cela n'affecte en rien la fourche.

Dans le premier cas, vous vous retrouvez avec 8 processus sans rien à écrire, car le tampon de sortie a déjà été vidé (en raison du \n).

Dans le second cas, vous avez encore 8 processus, chacun avec un tampon contenant "Hello world ..." et le tampon est écrit à la fin du processus.

18
edc65

@Kusalananda a expliqué pourquoi la sortie est répétée. Si vous êtes curieux de savoir pourquoi la sortie est répétée 8 fois et pas seulement 4 fois (le programme de base + 3 fourchettes):

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

Le contexte important ici est que stdout doit être ligne mise en mémoire tampon par la norme comme configuration par défaut.

Cela provoque un \n Pour vider la sortie.

Étant donné que le deuxième exemple ne contient pas la nouvelle ligne, la sortie n'est pas vidée et comme fork() copie l'intégralité du processus, elle copie également l'état du tampon stdout.

Maintenant, ces appels fork() dans votre exemple créent au total 8 processus - tous avec une copie de l'état du tampon stdout.

Par définition, tous ces processus appellent exit() lors du retour de main() et exit() appels fflush() suivis de fclose() sur tous les flux stdio actifs. Cela inclut stdout et par conséquent, vous voyez le même contenu huit fois.

Il est recommandé d'appeler fflush() sur tous les flux avec une sortie en attente avant d'appeler fork() ou de laisser l'appel fourchu appeler explicitement _exit() qui ne quitte le processus sans vidage les flux stdio.

Notez que l'appel de exec() ne vide pas les tampons stdio, il est donc normal de ne pas se soucier des tampons stdio si vous (après avoir appelé fork()) appelez exec() et (si cela échoue), appelez _exit().

BTW: Pour comprendre qu'une mauvaise mise en mémoire tampon peut provoquer, voici un ancien bogue sous Linux qui a été récemment corrigé:

La norme exige que stderr ne soit pas mis en tampon par défaut, mais Linux a ignoré cela et a mis la ligne stderr en mémoire tampon (et pire encore) complètement en cas de redirection de stderr via un canal. Les programmes écrits pour UNIX ont donc produit des trucs sans retour à la ligne trop tard sur Linux.

Voir le commentaire ci-dessous, il semble être corrigé maintenant.

Voici ce que je fais pour contourner ce problème Linux:

    /* 
     * 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); 

Ce code ne fait pas de mal sur les autres plates-formes car appeler fflush() sur un flux qui vient d'être vidé est un noop.

4
schily