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 の処理