fork ()가있는 프로그램이 때때로 출력을 여러 번 인쇄하는 이유는 무엇입니까?


50

프로그램 1에서 Hello world한 번만 인쇄되지만 제거 \n하고 실행하면 (프로그램 2) 출력이 8 번 인쇄됩니다. 누군가 나에게 \n여기서 의 중요성 과 그것이 어떻게 영향을 미치는지 설명해 주 fork()시겠습니까?

프로그램 1

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

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

출력 1 :

hello world... 

프로그램 2

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

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

출력 2 :

hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...

10
파일 ( ./prog1 > prog1.out) 또는 파이프 ( ./prog1 | cat) 로 출력하여 프로그램 1을 실행하십시오 . 당신의 마음을 날려 버릴 준비를하십시오. :-) ⁠
G-Man

이 문제의 다른 변형을 다루는 관련 Q + A : C 시스템 (“bash”)은 stdin을 무시합니다
Michael Homer

13
이것은 가까운 투표를 모았으므로 "UNIX C API 및 시스템 인터페이스"에 대한 질문은 명시 적으로 허용 됩니다. 버퍼링 문제는 쉘 스크립트에서도 일반적으로 발생하며 fork()다소 유닉스 관련이므로 유닉스에 대한 주제가 상당히 많은 것 같습니다.
ilkkachu 2018 년

@ilkkachu 실제로, 당신이 그 링크를 읽고 그것이 참조하는 메타 질문을 클릭하면, 이것이 주제가 아닌 것을 매우 명확하게 설명합니다. 무언가가 C이고 유닉스에 C가 있기 때문에 주제를 다루지 않습니다.
Patrick

@ 패트릭, 실제로, 나는했다. 그리고 나는 그것이 여전히 "이유 내"조항에 맞다고 생각하지만, 물론 그것은 저에 불과합니다.
ilkkachu 2018 년

답변:


93

C 라이브러리 printf()기능을 사용하여 표준 출력으로 출력 할 때 출력은 일반적으로 버퍼링됩니다. 개행을 출력 fflush(stdout)하거나 프로그램을 호출 하거나 종료 할 때까지 버퍼는 플러시 되지 않습니다 _exit(). 표준 출력 스트림은 기본적으로 TTY에 연결될 때 이러한 방식으로 라인 버퍼링됩니다.

"프로그램 2"에서 프로세스를 분기하면 하위 프로세스는 플러시되지 않은 출력 버퍼를 포함하여 상위 프로세스의 모든 부분을 상속합니다. 이렇게하면 플러시되지 않은 버퍼가 각 자식 프로세스에 효과적으로 복사됩니다.

프로세스가 종료되면 버퍼가 플러시됩니다. 총 8 개의 프로세스 (원래 프로세스 포함)를 시작하면 각 개별 프로세스가 종료 될 때 비 플러시 버퍼가 플러시됩니다.

그것은의 각각 때문에 fork()두 번 당신이 전의 프로세스의 수를 얻을 fork()(그들은 무조건 때문에), 당신은이 (2 세이 3 = 8).


14
관련 : 당신이 종료 할 수 있습니다 main_exit(0)바로 세척 버퍼없이 종료 시스템 호출을 한 다음에 줄 바꿈없이 제로 시간을 출력됩니다. ( exit ()의 Syscall 구현_call_exit (0) (syscall로 종료)은 stdout 컨텐츠를 수신하지 못하게하는 방법은 무엇입니까? ) 또는 Program1을 파이프 cat하거나 파일로 리디렉션하여 8 번 인쇄되는 것을 볼 수 있습니다. (stdout은 TTY가 아닌 경우 기본적으로 전체 버퍼링됩니다). 또는 fflush(stdout)2nd 이전에 줄 바꿈이없는 경우에 추가하십시오 fork().
Peter Cordes

17

어떤 식 으로든 포크에 영향을 미치지 않습니다.

첫 번째 경우에는 출력 버퍼가 이미 비었기 때문에 (으로 인해 \n) 쓸 수없는 8 개의 프로세스가 생깁니다 .

두 번째 경우에는 여전히 "Hello world ..."를 포함하는 버퍼가있는 8 개의 프로세스가 있으며 프로세스 종료시 버퍼가 작성됩니다.


12

@Kusalananda는 출력이 반복되는 이유를 설명했다 . 왜 출력이 4 번이 아닌 8 번 반복되는지 궁금하다면 (기본 프로그램 + 3 포크) :

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
}

2
이것은 포크의 기본입니다
Prvt_Yadav

3
@Debian_yadav는 아마도 그 의미에 익숙한 경우에만 분명합니다. 예를 들어 플러시 stdio 버퍼 처럼 .
roaima

2
@Debian_yadav : en.wikipedia.org/wiki/False_consensus_effect- 모두가 모든 것을 알고 있다면 왜 질문해야합니까?
혼자 지덱

8
@Debian_yadav 나는 OP의 마음을 읽을 수 없으므로 모른다. 어쨌든, 스택 교환은 다른 사람들도 지식을 검색하는 곳이며 내 대답은 Kulasandra의 좋은 대답에 유용한 추가 기능이 될 수 있다고 생각합니다. 내 대답 은 Kulasandra가 2 시간 전에 말한 것을 반복하는 edc65와 비교하여 무언가 (기본적이지만 유용한) 것을 추가합니다 .
혼자 지덱

2
이것은 실제 답변이 아니라 답변에 대한 짧은 의견입니다. 질문에 대해 정확히 8 아니에요 이유 "여러 번"요청
파이프

3

여기서 중요한 배경은 기본 설정으로 표준에 의해 라인 버퍼링stdout 되어야 한다는 것 입니다 .

이로 인해 a \n가 출력을 플러시합니다.

두 번째 예에는 줄 바꿈이 포함되어 있지 않으므로 출력이 플러시되지 않고 fork()전체 프로세스가 복사되므로 stdout버퍼 상태도 복사 됩니다.

이제 fork()예제에서 이러한 호출은 총 8 개의 프로세스를 작성합니다.이 프로세스는 모두 stdout버퍼 상태의 사본으로 이루어 집니다.

정의에 의하면,이 모든 프로세스 호출 exit()에서 반환 할 때 main()exit()호출 fflush()다음에 fclose()모든 활성에 STDIO의 스트림. 여기에는 stdout동일한 내용이 8 번 표시됩니다.

그것은 좋은 연습이 호출하는 것입니다 fflush()호출하기 전에 출력을 대기중인 모든 스트림에 fork()또는 명시 적으로 갈래 아이 호출을 수 있도록 _exit()만이 표준 입출력 스트림을 플래시는 실시하지 않고 프로세스를 종료있다.

호출 exec()은 stdio 버퍼를 플러시하지 않으므로 호출 한 후 fork()call exec()및 (실패한 경우 ) call 경우 stdio 버퍼를 신경 쓰지 않아도됩니다 _exit().

BTW : 잘못된 버퍼링이 발생할 수 있음을 이해하기 위해 최근 수정 된 Linux의 이전 버그는 다음과 같습니다.

표준은 stderr기본적으로 버퍼링 해제가 필요 하지만 Linux는 이것을 무시 stderr하고 stderr가 파이프를 통해 경로 재 지정되는 경우 라인 버퍼링 및 (더 나쁜 것은) 완전히 버퍼링했습니다. 따라서 UNIX 용으로 작성된 프로그램은 Linux에서 너무 늦게 줄 바꿈없이 출력했습니다.

아래의 의견을 참조하십시오. 현재 수정 된 것 같습니다.

이것은이 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); 

이 코드 fflush()는 방금 플러시 된 스트림을 호출 하는 것이 noop 이기 때문에 다른 플랫폼에서는 해를 끼치 지 않습니다 .


2
아니요, stdout은 대화 형 장치가 아닌 경우 완전 버퍼링되어야하며,이 경우 지정되지 않지만 실제로는 라인 버퍼링됩니다. stderr은 완전히 버퍼링되지 않아야합니다. 참조 pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/...
스테판 Chazelas가에게

setbuf()데비안의 man 페이지 ( man7.org에있는 비슷한 페이지 )는 "표준 오류 스트림 stderr는 기본적으로 항상 버퍼링되지 않습니다."라고 말합니다. 간단한 테스트는 출력이 파일, 파이프 또는 터미널로 이동하는지 여부에 관계없이 그렇게 작동하는 것 같습니다. 그렇지 않으면 어떤 버전의 C 라이브러리에 대한 참조가 있습니까?
ilkkachu 2018 년

4
리눅스는 커널이고, stdio 버퍼링은 유저 랜드 기능이며, 커널은 여기에 관여하지 않습니다. Linux 커널에 사용 가능한 많은 libc 구현이 있으며, 서버 / 워크 스테이션 유형 시스템에서 가장 일반적으로 사용되는 GNU 구현은 stdout이 전체 버퍼링 (tty 인 경우 라인 버퍼링)되고 stderr이 버퍼링되지 않습니다.
Stéphane Chazelas 2016 년

1
@schily, 내가 실행 한 테스트 : paste.dy.fi/xk4 . 끔찍한 구식 시스템에서도 같은 결과를 얻었습니다.
ilkkachu

1
@schily 사실이 아닙니다. 예를 들어, 나는 musl을 대신 사용하는 Alpine Linux를 사용 하여이 의견을 작성하고 있습니다.
NieDzejkob 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.