No Programa 1 Hello world
é impresso apenas uma vez, mas quando eu removo \n
e 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();
}
Saída 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();
}
Saída 2:
hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...
Ao enviar para a saída padrão usando a
printf()
função da biblioteca C, a saída geralmente é armazenada em buffer. O buffer não é liberado até que você produza uma nova linha, chamefflush(stdout)
ou saia do programa (mas não através da chamada_exit()
). O fluxo de saída padrão é, por padrão, com buffer de linha dessa maneira quando conectado a um TTY.Quando você bifurca o processo no "Programa 2", os processos filho herdam todas as partes do processo pai, incluindo o buffer de saída não liberado. Isso copia efetivamente 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 no término de cada processo individual.
É oito porque em cada um
fork()
você tem o dobro de processos que tinha antes dofork()
(já que eles são incondicionais), e você tem três desses (2 3 = 8).Não afeta o garfo de forma alguma.
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.
@Kusalananda explicou por que a saída é repetida . Se você está curioso por que a saída é repetida 8 vezes e não apenas 4 vezes (o programa base + 3 forks):
O contexto importante aqui é que
stdout
é necessário ter buffer de linha pelo padrão como configuração padrão.Isso faz
\n
com que a limpe a saída.Como o segundo exemplo não contém a nova linha, a saída não é liberada e, ao
fork()
copiar todo o processo, também copia o estado dostdout
buffer.Agora, essas
fork()
chamadas em seu exemplo criam 8 processos no total - todos eles com uma cópia do estado dostdout
buffer.Por definição, todos esses processos chamam
exit()
ao retornar demain()
eexit()
chamadasfflush()
seguidas por em todos os fluxos stdiofclose()
ativos . Isso inclui e, como resultado, você vê o mesmo conteúdo oito vezes.stdout
É uma boa prática chamar
fflush()
todos os fluxos com saída pendente antes de chamarfork()
ou permitir que o filho bifurcado chame explicitamente_exit()
que apenas sai do processo sem liberar os fluxos stdio.Observe que a chamada
exec()
não libera os buffers stdio, portanto, não há problema em não se preocupar com os buffers stdio se você (após chamarfork()
) chamarexec()
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
stderr
ser unbuffered por padrão, mas o Linux ignorou isso e fezstderr
buffer de linha e (pior ainda) totalmente bufferizado caso o stderr fosse redirecionado através de um pipe. Portanto, programas escritos para UNIX produziram coisas sem nova linha tarde demais no Linux.Veja o comentário abaixo, parece estar corrigido agora.
Isto é o que eu faço para contornar este problema do Linux:
Esse código não prejudica em outras plataformas, pois chamar
fflush()
um fluxo que acabou de ser liberado é um noop.