複数の処理が同じパイプへログを送り、一つの処理がまとめて読み取る。そのとき、ある書き手の一行の途中へ、別の書き手の文字が入り込むと、受信側は記録を正しく解釈できません。Linuxのパイプには、小さな書き込みを他の書き込みと混ぜないための上限、PIPE_BUFがあります。
ただし、この保証は「一回のwriteに対応して、一回のreadが返る」という意味ではありません。書き込み同士が混ざらないことと、読み取りが記録の境界を保つことは別です。通常のパイプのバイトストリームを対象に、その違いを整理します。[1]
簡単にまとめると?
- LinuxではPIPE_BUFは4096バイトです。この範囲内の一回の書き込みは、ほかの書き手のデータが途中に入り込まない単位として扱われます。
- PIPE_BUFはパイプ全体の容量とは別です。保証される単位が小さくても、空き容量が足りなければ待機したり、非ブロッキングではEAGAINになったりします。
- 通常のパイプは読み取り側へメッセージ境界を伝えません。長さや区切りの規則を用意し、分割されても連結されても記録を復元できるようにします。
原子的とは、別の書き込みが途中に入らないこと
ここでの原子的な書き込みは、渡したバイト列が、他のプロセスによる書き込みと交互に混ざらず、連続した並びとしてパイプへ入ることを指します。CPUの不可分命令や、記録をディスクへ永続化する話とは別です。[1、PIPE_BUF]
二つの書き手が、それぞれ12バイトの記録Aと記録Bを一回ずつwriteへ渡したとします。両者が原子性の条件を満たしていれば、受信するバイト列の並びは、例えばA全体の後にB全体、またはB全体の後にA全体です。Aの前半、Bの一部、Aの後半というように、一回の書き込みの内側へ別の書き込みが入ることはありません。
どちらの記録が先になるかまで、この保証から決まるわけではありません。同時に動く処理の順序や、公平に交代すること、待ち時間の上限を約束する条件でもありません。「混ざらない」と「望んだ順番で届く」を分けて考えます。
4096バイトの境界と、パイプの空き容量
Linux man-pagesの説明では、LinuxのPIPE_BUFは4096バイトです。書き込む長さnがこの値以下の場合と、超える場合を分けています。POSIXが要求する最小値は512バイトなので、Linuxの4096という値を、すべての環境に共通する定数として使うことはできません。[1]
数えるのは文字数ではなく、writeへ渡すバイト数です。日本語を含む記録をUTF-8で符号化すると、一文字が複数バイトになることがあります。見た目の文字数が同じでも、送る長さは同じとは限りません。ヘッダーや区切り文字も含めて、一回の書き込みの全体を数えます。
また、PIPE_BUFとパイプの容量は異なります。容量は、まだ読み取られていないデータをどれだけ保持できるかに関係します。空きが多いという理由だけで、PIPE_BUFを超える書き込みまで原子的になるわけではありません。反対に、小さな書き込みでも、今の空きが少なければ直ちに完了できるとは限りません。
待つ方式と、待たない方式で結果が変わる
ブロッキング方式でnがPIPE_BUF以下なら、nバイトを原子的に書き込みます。ただし、すぐにnバイト分を入れる空きがなければ、writeは待つことがあります。原子性を満たすことと、呼び出しが即座に戻ることは独立した条件です。
O_NONBLOCKを指定した非ブロッキング方式では、nがPIPE_BUF以下で、必要な空きがあれば全nバイトを書き込みます。空きが足りなければ、途中の一部だけを入れる代わりに、EAGAINで失敗します。この場合は「今はこの単位を書けない」という扱いです。[1]
一方、nがPIPE_BUFを超えると、別の書き手のデータが途中へ入る可能性があります。非ブロッキング方式では、空きがあっても要求した全長より短い書き込みになる場合があり、戻り値が実際に受け付けられたバイト数を示します。満杯ならEAGAINです。書き手は、長さの大小と待機方式を組み合わせて判断する必要があります。
これらは通常の容量条件についての整理です。シグナル割込みや、読み取り端がすべて閉じられた場合など、別の失敗条件までなくなるわけではありません。原子性を理由に戻り値やエラーの確認を省かないようにします。
原子的に書いても、8バイトずつ読めば記録は分かれる
先ほどのA、Bがそれぞれ12バイトで、A全体の後にB全体が書かれたとします。受信側が8バイトずつ取得し、その都度8バイトを受け取れる場合を考えると、最初はAの先頭8バイト、次はAの残り4バイトとBの先頭4バイト、最後はBの残り8バイトになります。

二回目のreadにAとBの両方が含まれていても、原子的な書き込みの保証には反しません。パイプの中ではAとBが途中で混ざらずに並んでいます。読み取り側が、その連続したバイト列を別の幅で区切っただけだからです。
通常のパイプはメッセージ境界を持たないため、一回のreadを一件の記録と決め付けることはできません。受信側には、前回の未完了部分を残し、新しく読んだ部分を足して、完全な記録がそろったところで取り出す処理が必要です。改行を区切りにするなら、本文中の改行をどう扱うかまで決めます。先頭に長さを置くなら、その長さ情報自体が分割される場合にも対応します。
一件を複数回に分けて書くと、保証の単位も変わる
例えば、記録のヘッダーをwriteし、次に本文を別のwriteで送る構成では、個々の長さがPIPE_BUF以下でも、その二回をまとめた一件の記録が原子的になるわけではありません。二つの呼び出しの間には、別の書き手がデータを入れられます。
一件の全体が上限以内なら、ヘッダーや区切りも含めて一つのバッファへ組み立て、一回の書き込みへ渡す設計が候補になります。一件が上限を超えるなら、すべての書き手を含めた排他制御、書き込みを一つの担当へ集約する方式、記録境界を扱える別の通信方式などを検討します。ただ長い記録を小片へ分けただけでは、小片の間に別の記録が入る問題は残ります。
受信側の長さ情報は記録を区切る助けになりますが、それだけで送信側の混在を防ぐものではありません。複数の書き手が同じ通信路を使う場合には、バイト列を混ぜずに入れる方法と、受信したバイト列から記録を取り出す方法の両方が必要です。
ログが壊れる原因を追うときは、一件の符号化後の長さ、一件あたりのwrite回数、同じパイプへ書く主体の数、待機方式、readの戻り値の扱いを並べて確認します。パイプの容量を増やす前に、どの単位の原子性が保証され、どの境界はアプリケーションが作るのかを明確にすることが出発点になります。
出典
- [1] Linux man-pages 6.19 — pipe(7)、I/O on pipes and FIFOs、Pipe capacity、PIPE_BUF。通常のパイプのバイトストリーム、原子的書き込みの四条件、Linuxの値とPOSIXの最小値を参照。2026年10月22日保存版。図と12バイト二件の読み取り例は独自のモデルで、Linuxカーネルを実行した測定ではありません。
