go-pipe - 解説

実装の詳細

パイプの作成

pr, pw, err := os.Pipe()
// pr: パイプの読み取り側
// pw: パイプの書き込み側

パイプは単方向です。データは pw に書き込まれ、pr から読み取られます。

ファイルディスクリプタの複製

dup2 システムコールは、ファイルディスクリプタを複製します:

syscall.Dup2(newFd, oldFd)
// oldFd を閉じて、newFd の複製を oldFd に割り当て

プロセスの流れ

1. パイプを作成
2. fork で子プロセス1を作成
   - stdin を入力ファイルにリダイレクト
   - stdout をパイプの書き込み側にリダイレクト
   - cmd1 を exec
3. fork で子プロセス2を作成
   - stdin をパイプの読み取り側にリダイレクト
   - stdout を出力ファイルにリダイレクト
   - cmd2 を exec
4. 親プロセスで待機

よくある間違い

1. パイプを閉じ忘れる

// 間違い: パイプを閉じないと子プロセスがブロック
if err := cmd2.Wait(); err != nil { ... }

// 正しい: 書き込み側を閉じてからWait
go func() {
    cmd1.Wait()
    pw.Close()  // これが重要
}()

2. デッドロック

// 間違い: 順番に Wait するとデッドロック
cmd1.Wait()
cmd2.Wait()

// 正しい: 並列に実行
go func() { cmd1.Wait(); pw.Close() }()
cmd2.Wait()

3. ゾンビプロセス

子プロセスが終了しても、親が Wait しないとゾンビプロセスになります。必ず Wait を呼びましょう。

最適化のヒント

  • バッファサイズ: 大きなデータを扱う場合、バッファサイズを調整
  • エラー伝播: 子プロセスの終了ステータスを確認
  • シグナルハンドリング: SIGPIPE の処理