unix domain socket是go跨进程ipc首选方案,需用绝对路径、父目录设x权限、启动前os.removeall清理旧socket,客户端应重试连接;os.pipe()仅限父子进程,不可用于独立进程通信。

Go语言进程间通信(IPC)在复杂系统中不是“选哪种机制”的问题,而是“哪些机制能真正跨进程、哪些只是看起来像IPC”的问题。标准库的 os.Pipe() 不能用于任意两个 Go 进程;channel 只在单个进程内有效;真正可用的跨进程方案只有 Unix Domain Socket、命名管道(FIFO)、共享内存(需额外封装)、消息队列(如 POSIX MQ 或第三方库),以及 gRPC/HTTP 等网络层抽象。
Unix Domain Socket 是本地 IPC 的事实标准
同一台机器上两个独立 Go 进程通信,net.Listen("unix", path) 是首选。它比 TCP loopback 快 2–3 倍,且支持文件权限控制。
- 路径必须是绝对路径,比如
/tmp/myapp.sock,相对路径会直接失败 - 父目录必须有执行(
x)权限,否则客户端dial时返回permission denied - 服务端启动前务必清理旧 socket 文件:
os.RemoveAll(socketPath)(不能只用os.Remove,因为 socket 文件可能是“伪文件”) - 客户端连接失败时别 panic,应重试(例如 3 次,间隔 100ms),因为服务端
Listen和Accept之间存在微小窗口 - 用
SOCK_STREAM(默认),不要碰SOCK_DGRAM——后者在 Unix domain 场景下极少使用,且 Go 标准库不直接支持可靠投递
命名管道(FIFO)只适合简单场景,且容易卡住
mkfifo /tmp/myfifo 创建的 FIFO 是真正的跨进程通道,但它不是“即开即用”的:一端 open 会阻塞,直到另一端也 open,且读写方向固定。
- 必须用
os.OpenFile("/tmp/myfifo", os.O_RDONLY, 0)和os.OpenFile("/tmp/myfifo", os.O_WRONLY, 0)分别打开,不能混用 - 不能 seek,不支持双向通信;想双向就得开两个 FIFO,或直接换 UDS
- 如果看到程序 hang 在
open,大概率是 reader 和 writer 启动顺序不对,或没加超时(syscall.Openat+syscall.O_NONBLOCK可破) - FIFO 不是 Go 特有机制,C/Python/Shell 都能用,适合多语言混合系统中做轻量级桥接
gRPC 不是 IPC,但常被当作 IPC 替代方案
当两个 Go 进程部署在同一台机器,又需要强类型、流式、带超时/重试的通信时,很多人会跳过 UDS 直接上 gRPC。这不是错,但得清楚代价:
- gRPC 默认走 HTTP/2 over TCP,哪怕你 bind 到
localhost:50051,仍要经过内核网络栈、TCP 状态机、TLS(若启用)等开销 - 如果你用
grpc.WithTransportCredentials(insecure.NewCredentials())并绑定到unix:///tmp/myapp.grpc,那底层其实还是 UDS —— 但这是 gRPC 的扩展能力,不是标准行为,需确认所用版本支持 - 调试成本高:抓包要用
tcpdump -i lo或专门的 gRPC CLI 工具,不像 UDS 可直接nc -U /tmp/myapp.sock测试 - 真正优势在于跨语言兼容性:C++/Rust/Java 客户端能无缝接入,而纯 Go 的 UDS 服务别人调不了
共享内存和消息队列需要额外封装或依赖
Go 标准库不提供 shmget/msgsnd 封装,直接 syscall 调用既难维护又易出错。除非你明确需要零拷贝或严格消息顺序,否则优先考虑已有成熟封装:
-
siadat-ipc提供了基于 POSIX MQ 的 Go 封装,支持多生产者/消费者、消息过滤、持久化,但引入新依赖 -
golang-ipc更侧重跨平台(Linux/macOS/Windows),内部对不同系统做适配,适合嵌入式或桌面应用 - 自己用
syscall.Mmap+sync/atomic实现共享内存?可行,但必须处理内存屏障、缓存一致性、进程生命周期管理——90% 的场景没必要 - 别用
os/exec.Cmd.StdinPipe()试图模拟 IPC:它只对子进程有效,且父子关系一旦断开就失效
真正容易被忽略的点是:IPC 的边界从来不在代码里,而在进程生命周期和部署拓扑中。一个用 UDS 通信的服务,如果被 systemd 重启时没清理 socket 文件,或者容器中挂载路径权限不对,再好的 Go 代码也会连不上。写完 net.Listen("unix", ...) 后,先手动 ls -l /tmp/myapp.sock 看权限,再 strace -e trace=connect,bind,openat go run main.go 看系统调用,比读十遍文档都管用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











