syscall.shmopen在linux/macos上创建的是内核级posix共享内存对象,不落盘、无文件路径,需配合shm_unlink自动清理;而mmap普通文件虽可行,但有日志开销、需ftruncate预设大小、残留脏数据等隐性成本。

syscall.ShmOpen 在 Linux/macOS 上能用,但不是“文件共享内存”——它创建的是 POSIX 共享内存对象,不落盘、无文件路径,只是内核中一块带名字的内存页;而真正通过「文件」实现的共享内存,是 mmap 映射一个普通文件(如 /tmp/shm.dat),靠文件系统做载体。两者性能、生命周期、清理方式完全不同。
别把 mmap 文件当共享内存用,除非你清楚代价
用 os.OpenFile + syscall.Mmap 映射一个真实文件,确实能让多个进程读写同一块内存区域,但它带来三个隐性成本:
- 每次写入都触发文件系统日志(ext4/xfs 默认启用 journal),实际写入磁盘或 page cache,延迟远高于纯内存操作
- 若文件未提前
ftruncate到目标大小,mmap会失败或映射长度不足——Go 的os.File不暴露 fd 给syscall.Mmap,必须用syscall.Open或syscall.Dup获取原始 fd - 进程崩溃时,文件内容残留,下次启动可能读到脏数据;而
shm_open配合shm_unlink可自动清理(只要最后一个 fd 关闭)
Go 中 mmap 普通文件的正确写法(Linux/macOS)
绕过 os.File 封装,直接用 syscall 操作 fd:
fd, err := syscall.Open("/tmp/shm.dat", syscall.O_RDWR|syscall.O_CREAT, 0600)
if err != nil {
panic(err)
}
defer syscall.Close(fd)
// 必须先截断,否则 mmap 失败
syscall.Ftruncate(fd, 4096)
data, err := syscall.Mmap(fd, 0, 4096, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
panic(err)
}
defer syscall.Munmap(data) // 注意:不是 os.File.Close!
// 现在 data 是 []byte,可跨进程读写
关键点:
-
syscall.Open返回的是 raw fd,syscall.Mmap要求这个 fd 已打开且可读写 -
syscall.Munmap必须调用,否则内存泄漏;os.File.Close不释放 mmap 区域 - 映射后仍需自行同步——
sync/atomic对跨进程无效,得用syscall.Semget或预先在共享内存里放pthread_mutex_t
为什么 os.Pipe 和 net.UnixConn 更值得优先考虑
它们不是共享内存,但对绝大多数 Go 服务已足够快:
-
net.UnixConn在本地 loopback 上实测吞吐 >100MB/s,延迟 -
os.Pipe是内核 pipe buffer,零拷贝只发生在 kernel space,用户态仍要Read/Write系统调用,但比序列化 JSON + HTTP 省掉编解码开销 - 两者都不需要手动处理页对齐、信号安全、进程退出时资源残留——Go runtime 自动回收
只有当你实测发现 IPC 成为瓶颈(比如单次通信 shm_open + Mmap。
跨语言互通时,文件映射反而更稳
如果 Go 进程要和 Python/C++ 进程共享状态,用文件映射比 POSIX shm 更兼容:
- Python 的
mmap.mmap、C++ 的std::filesystem::mapped_file都原生支持普通文件映射,无需额外链接-lrt - Windows 上只能用
CreateFileMapping+MapViewOfFile,它本质也是文件映射(哪怕用INVALID_HANDLE_VALUE创建匿名映射) - 调试时可直接
hexdump /tmp/shm.dat查看内容,而/dev/shm/myshm无法用常规工具 inspect
但务必注意:所有进程必须约定好结构体 layout(例如用 binary.Write 写固定长度字段)、字节序,并在首次写入前用 ftruncate 清零——否则 mmap 出来的内存页内容是未定义的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











