Por que um programa com fork () às vezes imprime sua saída várias vezes?

45

No Programa 1 Hello world é impresso apenas uma vez, mas quando eu removo \n e o executo (Programa 2), a saída é impressa 8 vezes. Alguém pode me explicar o significado de \n aqui e como isso afeta o fork() ?

Programa 1

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

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

Resultado 1:

hello world... 

Programa 2

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

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

Resultado 2:

hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...
    
por lmaololrofl 05.06.2018 / 05:05

4 respostas

86

Ao enviar para saída padrão usando a função printf() da biblioteca C, a saída é geralmente armazenada em buffer. O buffer não é liberado até que você emita uma nova linha, chame fflush(stdout) ou saia do programa (não chamando _exit() ). O fluxo de saída padrão é, por padrão, armazenado em buffer dessa maneira quando conectado a um TTY.

Quando você bifurca o processo, os processos filho herdam todas as partes do processo pai, incluindo o buffer de saída sem limpeza. Isso efetivamente copia o buffer não-liberado para cada processo filho.

Quando o processo termina, os buffers são liberados. Você inicia um total geral de oito processos (incluindo o processo original) e o buffer não liberado será liberado na finalização de cada processo individual.

É oito porque a cada fork() você obtém o dobro do número de processos que você tinha antes do fork() (já que eles são incondicionais), e você tem três deles (2 3 = 8).

    
por 05.06.2018 / 06:24
16

Não afeta de forma alguma o garfo.

No primeiro caso, você acaba com 8 processos sem nada para escrever, porque o buffer de saída já foi esvaziado (devido ao \n ).

No segundo caso você ainda tem 8 processos, cada um com um buffer contendo "Hello world ..." e o buffer é escrito no final do processo.

    
por 05.06.2018 / 08:42
10

@Kusalananda explicou por que a saída é repetida . Se você está curioso porque a saída é repetida 8 vezes e não apenas 4 vezes (o programa base + 3 forks):

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
}
    
por 05.06.2018 / 08:37
3

O contexto importante aqui é que stdout é necessário para ser line buffered pelo padrão como configuração padrão.

Isso faz com que \n libere a saída.

Como o segundo exemplo não contém a nova linha, a saída não é liberada e, como fork() copia todo o processo, ele também copia o estado do buffer stdout .

Agora, essas chamadas fork() em seu exemplo criam 8 processos no total - todos eles com uma cópia do estado do buffer stdout .

Por definição, todos esses processos chamam exit() ao retornar de main() e exit() chamadas fflush() seguido por fclose() em todos os fluxos stdio ativos. Isso inclui stdout e, como resultado, você vê o mesmo conteúdo oito vezes.

É uma boa prática chamar fflush() em todos os fluxos com saída pendente antes de chamar fork() ou permitir que o filho bifurcado chame explicitamente _exit() que apenas sai do processo sem liberar os fluxos stdio.

Observe que chamar exec() não libera os buffers stdio, portanto, não há problema nenhum em armazenar os buffers se você (após chamar fork() ) chamar exec() e (se isso falhar) chamar _exit() .

BTW: Para entender que o buffer errado pode causar, aqui está um antigo bug no Linux que foi corrigido recentemente:

O padrão requer que stderr seja unbuffered por padrão, mas o Linux ignorou isso e fez stderr line bufferizar e (pior ainda) armazenar completamente buffer no caso de o stderr ser redirecionado através de um pipe. Então, programas escritos para UNIX produziram coisas sem a nova linha, muito tarde no Linux.

Veja o comentário abaixo, parece ser corrigido agora.

Isso é o que eu faço para contornar esse problema do 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); 

Esse código não prejudica outras plataformas, pois chamar fflush() em um fluxo que acabou de ser liberado é noop.

    
por 05.06.2018 / 11:10

Tags