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 \n
daqui 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...
./prog1 > prog1.out
) ou um pipe (./prog1 | cat
). Se prepare para ficar extremamente surpreso. :-) fork()
são específicos para unix, portanto, parece que isso é bastante tópico para o unix.SE.Respostas:
Ao produzir 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é você emitir uma nova linha, chamarfflush(stdout)
ou sair do programa (_exit()
embora não seja através da chamada ). O fluxo de saída padrão é, por padrão, armazenado em buffer de linha desta maneira quando conectado a um TTY.Quando você bifurca o processo no "Programa 2", os processos filhos herdam todas as partes do processo pai, incluindo o buffer de saída não liberado. 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 no final de cada processo individual.
São oito, porque em cada um
fork()
você obtém o dobro do número de processos que tinha antesfork()
(já que são incondicionais), e você tem três deles (2 3 = 8).fonte
main
com_exit(0)
apenas fazer uma chamada ao sistema de saída sem liberar buffers e, em seguida, ela será impressa zero vezes sem uma nova linha. ( A implementação em Syscall de exit () e Como é que _exit (0) (saindo por syscall) me impede de receber qualquer conteúdo stdout? ). Ou você pode canalizar o Program1cat
ou redirecioná-lo para um arquivo e vê-lo ser impresso 8 vezes. (o padrão stdout é totalmente armazenado em buffer quando não é um TTY). Ou adicionar umfflush(stdout)
para o caso de não-nova linha antes da 2ªfork()
...Não afeta o garfo de forma alguma.
No primeiro caso, você acaba com 8 processos sem nada para gravar, porque o buffer de saída já estava esvaziado (devido ao
\n
).No segundo caso, você ainda possui 8 processos, cada um com um buffer contendo "Hello world ..." e o buffer é gravado no final do processo.
fonte
@Kusalananda explicou por que a saída é repetido . Se você está curioso para saber por que a saída é repetida 8 vezes e não apenas 4 vezes (o programa base + 3 garfos):
fonte
O pano de fundo importante aqui é que
stdout
é necessário que a linha seja armazenada em buffer pelo padrão como configuração padrão.Isso faz com que
\n
a descarga da 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, também copia o estado dostdout
buffer.Agora, essas
fork()
chamadas no seu exemplo criam 8 processos no total - todos com uma cópia do estado dostdout
buffer.Por definição, todos esses processos são chamados
exit()
ao retornarmain()
eexit()
chamadasfflush()
seguidosfclose()
em todos os fluxos de stdio ativos . Isso incluistdout
e, como resultado, você vê o mesmo conteúdo oito vezes.É 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 só sai do processo sem liberar os fluxos de stdio.Observe que a chamada
exec()
não libera os stdio buffers, portanto, não há problema em se preocupar com os stdio buffers se você (após a chamadafork()
) ligarexec()
e (se isso falhar) ligar_exit()
.BTW: Para entender que o buffer incorreto pode causar, eis um bug anterior no Linux que foi corrigido recentemente:
O padrão requer
stderr
que não sejastderr
armazenado o buffer por padrão, mas o Linux ignorou isso e tornou a linha armazenada em buffer e (ainda pior) totalmente armazenada em buffer, caso o stderr fosse redirecionado através de um canal. Assim, os 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 solucionar esse problema do Linux:
Esse código não faz mal a outras plataformas, já que chamar
fflush()
um fluxo que acabou de ser liberado é um noop.fonte
setbuf()
, no Debian ( a página man7.org é semelhante ), declara que "o padrão stderr de fluxo de erro é sempre sem buffer por padrão." e um teste simples parece agir dessa maneira, independentemente de a saída ir para um arquivo, um tubo ou um terminal. Você tem alguma referência para qual versão da biblioteca C faria de outra maneira?