W Programie 1 Hello world
jest drukowany tylko raz, ale kiedy go wyjmuję \n
i uruchamiam (Program 2), wydruk jest drukowany 8 razy. Czy ktoś może mi wyjaśnić znaczenie \n
tutaj i jak to wpływa na fork()
?
Program 1
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world...\n");
fork();
fork();
fork();
}
Wyjście 1:
hello world...
Program 2
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world...");
fork();
fork();
fork();
}
Wyjście 2:
hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...
./prog1 > prog1.out
) lub potoku (./prog1 | cat
). Przygotuj się na zawrót głowy. :-) fork()
są również nieco specyficzne dla Uniksa, więc wydaje się, że jest to dość temat na unix.SE.Odpowiedzi:
Podczas wyświetlania na standardowe wyjście przy użyciu funkcji biblioteki C
printf()
, dane wyjściowe są zwykle buforowane. Bufor nie jest opróżniany, dopóki nie zostanie wypisany znak nowej linii, wywołaniefflush(stdout)
lub wyjście z programu (_exit()
choć nie przez wywołanie ). Standardowy strumień wyjściowy jest domyślnie buforowany w ten sposób, gdy jest podłączony do TTY.Po rozwidleniu procesu w „Programie 2” procesy podrzędne dziedziczą każdą część procesu nadrzędnego, w tym również niepoprawiony bufor wyjściowy. To skutecznie kopiuje niewypełniony bufor do każdego procesu potomnego.
Po zakończeniu procesu bufory są opróżniane. Zaczynasz w sumie osiem procesów (w tym proces pierwotny), a niewypełniony bufor zostanie opróżniony po zakończeniu każdego procesu.
Jest ósma, ponieważ za każdym razem
fork()
dostajesz dwa razy więcej procesów, które miałeś przedfork()
(ponieważ są one bezwarunkowe), a ty masz trzy z nich (2 3 = 8).źródło
main
się_exit(0)
po prostu zrobić wywołanie systemowe wyjście bez spłukiwania buforów, a następnie zostanie ona wydrukowana zero razy bez nowej linii. ( Implementacja exit () w Syscall i dlaczego _exit (0) (wyjście przez syscall) uniemożliwia mi otrzymywanie jakiejkolwiek treści standardowej? ). Lub możesz potokować Program1 docat
lub przekierować go do pliku i zobaczyć, jak drukuje się 8 razy. (standardowe wyjście jest buforowane domyślnie, gdy nie jest to TTY). Lub dodajfflush(stdout)
do przypadku no-newline przed drugimfork()
...W żaden sposób nie wpływa na widelec.
W pierwszym przypadku powstaje 8 procesów, które nie mają nic do napisania, ponieważ bufor wyjściowy został już opróżniony (z powodu
\n
).W drugim przypadku nadal masz 8 procesów, każdy z buforem zawierającym „Hello world ...”, a bufor jest zapisywany na końcu procesu.
źródło
@Kusalananda wyjaśnił, dlaczego dane wyjściowe są powtarzane . Jeśli jesteś ciekawy, dlaczego dane wyjściowe są powtarzane 8 razy, a nie tylko 4 razy (program podstawowy + 3 widelce):
źródło
Ważnym tłem jest to, że
stdout
zgodnie z ustawieniami domyślnymi wymagane jest buforowanie linii przez standard.Powoduje to
\n
opróżnienie wyjścia.Ponieważ drugi przykład nie zawiera nowej linii, dane wyjściowe nie są opróżniane, a ponieważ
fork()
kopiuje cały proces, kopiuje również stanstdout
bufora.Teraz te
fork()
wywołania w twoim przykładzie tworzą łącznie 8 procesów - wszystkie z kopią stanustdout
bufora.Z definicji wszystkie te procesy wywołują
exit()
po powrocie,main()
a następnieexit()
wywołania na wszystkich aktywnych strumieniach stdio . Obejmuje to, w wyniku czego widzisz tę samą treść osiem razy.fflush()
fclose()
stdout
Dobrą praktyką jest wywoływanie
fflush()
wszystkich strumieni z oczekującymifork()
danymi wyjściowymi przed wywołaniem lub zezwolenie na rozwidlone wywołanie potomne,_exit()
które wychodzi z procesu bez opróżniania strumieni stdio.Zauważ, że dzwonienie
exec()
nie opróżnia buforów stdio, więc nie martw się o bufory stdio, jeśli (po wywołaniufork()
) zadzwoniszexec()
i (jeśli to się nie powiedzie)_exit()
.BTW: Aby zrozumieć, że może to spowodować nieprawidłowe buforowanie, oto poprzedni błąd w systemie Linux, który został niedawno naprawiony:
Standard wymaga
stderr
domyślniestderr
buforowania bufora, ale Linux zignorował to i sprawił, że linia została buforowana i (jeszcze gorzej) całkowicie buforowana na wypadek, gdyby stderr został przekierowany przez potok. Więc programy napisane dla UNIXa wypisały rzeczy bez Linii zbyt późno w Linuksie.Patrz komentarz poniżej, wydaje się, że jest to teraz naprawione.
Oto co robię, aby obejść ten problem z Linuksem:
Ten kod nie wyrządza szkody na innych platformach, ponieważ wywołanie
fflush()
strumienia, który został właśnie opróżniony, jest noop.źródło
setbuf()
na Debianie ( ta na man7.org wygląda podobnie ) stwierdza, że „Standardowy strumień błędów stderr jest zawsze domyślnie niebuforowany”. prosty test wydaje się działać w ten sposób, niezależnie od tego, czy dane wyjściowe trafią do pliku, potoku czy terminala. Czy masz jakieś odniesienia do tego, która wersja biblioteki C zrobiłaby inaczej?