fork()を使用したプログラムが出力を複数回印刷することがあるのはなぜですか?


50

プログラム1では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-マンは「元に戻すモニカ言う

この問題の別のバリアントをカバーする関連するQ + A:Cシステム(「bash」)はstdinを無視します
Michael Homer

13
これにより、いくつかの賛成票が集められました。そのため、「UNIX C APIおよびシステムインターフェイス」に関する質問は明示的に許可されています。バッファリングの問題は、シェルスクリプトでもよく発生する問題であり、fork()Unix特有の問題でもあるため、unix.SEにとっては非常に話題になっているようです。
-ilkkachu

実際、@ ilkkachuは、そのリンクを読んで、それが参照するメタ質問をクリックすると、これがトピック外であることを非常に明確に綴ります。何かがCであり、unixがCを持っているからといって、それを話題にしない。
パトリック

@パトリック、実際、私はやった。そして、私はそれが「理由内」条項に適合すると考えていますが、もちろんそれは私だけです。
-ilkkachu

回答:


93

Cライブラリのprintf()関数を使用して標準出力に出力する場合、出力は通常バッファリングされます。バッファーは、改行を出力するfflush(stdout)か、プログラムを呼び出すか終了するまで(_exit()ただし、呼び出しではなく)フラッシュされません。標準出力ストリームは、TTYに接続されている場合、デフォルトでこの方法でラインバッファリングされます。

「プログラム2」でプロセスを分岐すると、子プロセスは、フラッシュされていない出力バッファを含む親プロセスのすべての部分を継承します。これにより、フラッシュされていないバッファが各子プロセスに効果的にコピーされます。

プロセスが終了すると、バッファがフラッシュされます。合計8つのプロセス(元のプロセスを含む)を開始すると、個々のプロセスが終了するたびに、フラッシュされていないバッファーがフラッシュされます。

それはだ8それぞれであるためfork()、あなたが前に持っていたプロセスの倍の数を取得するfork()(彼らは無条件であるため)、及びこれらの(2の3持っている3 = 8)。


14
関連:あなたが終了することができますmain_exit(0)、単にバッファをフラッシュせずに退出システムコールを作るために、そして、それは改行せずにゼロ回印刷されます。(Syscallによるexit()およびHow come _exit(0)(syscallによる終了)により、stdoutコンテンツを受信できなくなりますか?)。または、Program1をパイプするcatか、ファイルにリダイレクトして、8回印刷されるのを確認できます。(stdoutは、TTYではない場合、デフォルトでフルバッファされます)。または追加fflush(stdout)第二の前に無改行ケースにfork()...
ピーターコルド

17

フォークには影響しません。

最初のケースでは、出力バッファが既に空になっているため(書き込みのため)、書き込むものが何もない8つのプロセスになり\nます。

2番目のケースでは、まだ8つのプロセスがあり、それぞれに「Hello world ...」を含むバッファがあり、バッファはプロセス終了時に書き込まれます。


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バッファをフラッシュするように。
ロアイマ

2
@Debian_yadav:en.wikipedia.org/wiki/False_consensus_effect-誰もがすべてを知っているのに、なぜ質問する必要があるのですか?
ホンザジデック

8
@Debian_yadav OPの心が読めないのでわかりません。とにかく、stackexchangeは他の人も知識を探す場所であり、私の答えはKulasandraの良い答えに役立つ追加物になると思います。クラサンドラが2時間前に言ったことを繰り返すEDC65のものと比較して、私の答え何か(基本的だが有用)を追加します。
ホンザジデク

2
これは回答に対する短いコメントであり、実際の回答ではありません。質問は「複数回」ではない、それはまさに8.だ理由について尋ねる
パイプ

3

ここでの重要な背景は、デフォルト設定として標準によってラインバッファstdoutれる必要があることです。

これにより\n、出力がフラッシュされます。

2番目の例には改行が含まれていないため、出力はフラッシュされずfork()、プロセス全体がコピーされるので、stdoutバッファーの状態もコピーされます。

さて、fork()あなたの例のこれらの呼び出しは合計で8つのプロセスを作成します-それらのすべてはstdoutバッファの状態のコピーを持ちます。

定義によると、これらすべてのプロセスは、呼び出しexit()からの復帰時main()exit()通話がfflush()続くfclose()すべてのアクティブで標準入出力ストリーム。これにはstdout、結果として、同じコンテンツが8回表示されます。

呼び出しfflush()前に保留中の出力を持つすべてのストリームを呼び出すfork()_exit()、stdioストリームをフラッシュせずにプロセスを終了するだけの分岐子プロセスを明示的に呼び出すことをお勧めします。

呼び出しexec()はstdioバッファーをフラッシュしないので、(を呼び出した後fork())call exec()および(失敗した場合)callを呼び出してもstdioバッファーを気にしないでください_exit()

ところで:間違ったバッファリングが引き起こす可能性があることを理解するために、最近修正された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()フラッシュされたばかりのストリームを呼び出すことは何もしないので、このコードは他のプラットフォームに害を与えません。


2
いいえ、stdoutは対話型デバイスでない限り完全にバッファリングする必要がありますが、その場合は指定されていませんが、実際には行バッファリングされます。stderrは、完全にバッファリングされないことが必要です。参照してくださいpubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/...
ステファンChazelas

setbuf()Debianのmanページ(man7.orgのページも似ています)には、「デフォルトでは標準エラーストリームstderrは常にバッファリングされません」と記載されています。そして、出力がファイル、パイプ、またはターミナルのいずれに送られるかに関係なく、単純なテストがそのように動作するようです。そうしないと、Cライブラリのどのバージョンで参照できるのでしょうか?
-ilkkachu

4
Linuxはカーネルであり、stdioバッファリングはユーザーランド機能であり、カーネルはそこに関与していません。Linuxカーネルで使用できるlibc実装は多数あります。サーバー/ワークステーションタイプのシステムで最も一般的なのはGNU実装であり、stdoutはフルバッファー(ttyの場合はバッファー)、stderrはバッファーなしです。
ステファンシャゼラス

1
@schily、単なるテスト私が走った:paste.dy.fi/xk4を。私は恐ろしく古くなったシステムでも同じ結果を得ました。
-ilkkachu

1
@schilyそれは真実ではありません。たとえば、私はAlpine Linuxを使用してこのコメントを書いています。AlpineLinuxはmuslを代わりに使用しています。
-NieDzejkob
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.