os.pipe()仅适用于父子进程间通信,因其返回的内核匿名管道fd不带名字、不持久、不可跨独立进程访问;两个独立go run程序无法共享该fd,误用会导致broken pipe、立即eof或goroutine卡死。

os.Pipe() 不能用于两个独立 Go 进程通信,这是最常踩的坑。真要跨进程交互,得用系统级机制,不是靠内存管道。
为什么 os.Pipe() 在独立进程间会失败
它返回的是内核匿名管道 fd,只在 fork 后父子进程间继承有效;两个各自 go run main.go 启动的程序压根看不到对方的 fd。
- 常见错误现象:
broken pipe、read: EOF、goroutine 卡死在Read()或Write() - 本质混淆:把内存级管道当成了命名管道(FIFO)或 Unix Socket
- 调试线索:如果通信只在
exec.Command启动子进程时工作,但拆成两个独立二进制就失效——基本就是误用了os.Pipe()
Unix Domain Socket 是本地 IPC 的首选方案
性能远超 loopback TCP,支持文件权限控制,且 Go 原生支持。
- 服务端启动前必须调用
os.RemoveAll(socketPath)清理残留 socket 文件(os.Remove()对 socket 类型文件可能失败) -
socketPath必须是绝对路径,且其父目录要有可执行(x)权限,否则客户端连不上(Linux/Unix 路径遍历需要 x) - 监听后别写
defer listener.Close()在 accept 循环外;推荐defer func() { _ = listener.Close() }()或放在 shutdown 流程中 - 客户端连接失败时,别直接
panic;建议重试 3 次,每次time.Sleep(100 * time.Millisecond),因为服务端可能刚启动还没 listen 完
FIFO(命名管道)能用但限制多
适合简单单向、低频场景,比如日志转发或配置推送。
- 创建方式:
mkfifo /tmp/myapp.fifo,然后用os.OpenFile("/tmp/myapp.fifo", os.O_RDONLY, 0)和os.OpenFile("/tmp/myapp.fifo", os.O_WRONLY, 0)分别打开 - FIFO 是阻塞式:一端
open()时若另一端没开,就会卡住;必须并发启动 reader/writer,或加context.WithTimeout - 不支持双向通信 —— 要双向就得开两个 FIFO,不如直接换 UDS
- 不支持
seek,不能随机读写;也不支持os.Stat获取实时长度
框架集成时的关键取舍点
不要让 IPC 细节污染业务逻辑。用封装好的 transport 层隔离实现。
- 用
net.Listen("unix", path)时优先选SOCK_STREAM(默认),别自己搞SOCK_DGRAM;后者需显式用net.ListenUnixgram,实际极少用且难调试 - gin/http.Server 做 UDS 服务时,必须手动管理
listener生命周期:启动前清理、shutdown 时关闭、错误时重试 - Twirp/gRPC over UDS 是可行的,但要注意 protobuf 生成代码默认走 HTTP/2 over TCP;要改用 UDS,得自定义
http.Transport的 dialer,并确保 client/server 都配对 - 如果框架本身不支持 UDS(比如某些老版本 Echo),别硬套;宁可用标准
net/http+net.Listen("unix", ...)自建一层轻量 wrapper
真正容易被忽略的是权限和清理:socket 文件残留导致 address already in use,父目录缺 x 权限导致 permission denied,这两类错误在部署脚本里几乎从不检查,却占本地 IPC 故障的七成以上。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











