web-dev-qa-db-ja.com

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

プログラム1では_Hello world_が1回だけ出力されますが、_\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...
_
51
lmaololrofl

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

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

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

それはeightです。これは、各fork()fork()の前に持っていたプロセスの数の2倍になるためです(無条件であるため)。 (2 = 8)。

94
Kusalananda

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

最初のケースでは、出力バッファーが既に空になっているため(\n)。

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

18
edc65

@Kusalanandaは、出力が繰り返されるである理由を説明しました。出力が繰り返される理由に興味がある場合は8回であり、4回だけではありません(ベースプログラム+ 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
}
12
Honza Zidek

ここでの重要な背景は、stdoutが標準でデフォルト設定としてline bufferedである必要があることです。

これにより、_\n_が出力をフラッシュします。

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

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

定義により、これらのすべてのプロセスはexit()から戻るときにmain()を呼び出し、exit()fflush()を呼び出し、その後にfclose()を呼び出します。すべてのアクティブstdioストリーム。これにはstdoutが含まれるため、同じコンテンツが8回表示されます。

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

exec()を呼び出してもstdioバッファーはフラッシュされないので、(fork()を呼び出した後で)exec()を呼び出し、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()を呼び出すのは何もしないので、このコードは他のプラットフォームでは害を及ぼしません。

4
schily