
本文介绍如何在go中通过系统调用原生支持system v共享内存(shm),无需c++go,兼容linux(如ubuntu 12.04),安全高效地与c/c++等遗留程序共享大块数据。
Go官方哲学强调“通过通信共享内存”(Share memory by communicating),但这并不排斥对底层共享内存机制的支持——尤其当需与已有System V IPC程序(如问题中的应用A)协同工作时,直接使用shmget/shmat/shmdt/shmctl等系统调用是合理且高效的方案。
幸运的是,Go标准生态已提供成熟、安全的系统调用封装:golang.org/x/sys/unix 包。它为Linux/Unix平台提供了完备、类型安全的系统调用接口,完全避免了CGO带来的指针跨语言传递风险(如原问题中C.free()崩溃的根本原因:返回栈/全局变量地址被误当作堆内存释放)。
以下是在Ubuntu 12.04+环境下,使用纯Go实现共享内存读写的完整示例:
✅ 正确做法:纯Go调用System V shm(无CGO)
package main
import (
"fmt"
"unsafe"
"golang.org/x/sys/unix"
)
const (
shmKey = 0x12345678 // 与应用A约定的IPC key
shmSize = 1024 * 1024 // 1MB,按实际需求调整
shmFlags = unix.IPC_CREAT | 0666
)
func main() {
// 1. 创建或获取共享内存段
shmid, err := unix.Shmget(shmKey, shmSize, shmFlags)
if err != nil {
panic(fmt.Sprintf("shmget failed: %v", err))
}
fmt.Printf("Shared memory ID: %d\n", shmid)
// 2. 映射到当前进程地址空间(可读可写)
addr, err := unix.Shmat(shmid, nil, 0)
if err != nil {
panic(fmt.Sprintf("shmat failed: %v", err))
}
defer func() {
if err := unix.Shmdt(addr); err != nil {
fmt.Printf("WARNING: shmdt failed: %v\n", err)
}
}()
// 3. 安全访问内存:使用 unsafe.Slice(Go 1.17+)或手动转换
data := (*[shmSize]byte)(addr)[:shmSize:shmSize]
// 示例:读取前100字节(模拟应用A已写入的数据)
fmt.Printf("First 100 bytes (hex): %x\n", data[:100])
// 示例:写回结果(如状态码、处理长度等)
copy(data[:8], []byte("SUCCESS!"))
fmt.Println("Data processed and result written to shared memory.")
}⚠️ 关键注意事项
- 绝不混用CGO与非堆内存:原问题崩溃源于C函数返回全局数组buf地址,而Go错误调用C.free()试图释放该地址。C.free()仅适用于C.malloc/C.CString分配的内存。纯Go方案彻底规避此风险。
- 显式分离生命周期:Shmat返回的地址必须配对调用Shmdt;若需长期驻留,应由主进程管理映射,子goroutine仅读写。
- 同步至关重要:共享内存本身不提供同步机制。务必配合信号量(unix.Semget/unix.Semop)或文件锁(unix.Flock)协调A与B的读写顺序,防止竞态。
- 权限与清理:首次创建时使用IPC_CREAT;生产环境建议在退出前调用unix.Shmctl(shmid, unix.IPC_RMID, nil)删除段,避免残留。
- 跨平台提示:x/sys/unix主要面向Unix-like系统。Windows需改用syscall.CreateFileMapping等WinAPI(不在本文范围)。
✅ 总结
对于与遗留C程序通过System V共享内存协作的场景,优先采用golang.org/x/sys/unix包进行纯Go系统调用,而非CGO封装。它更安全、更可控、更易维护,且完全兼容Ubuntu 12.04及后续版本。只需三步:Shmget → Shmat → 安全切片访问,即可高效完成大数据量IPC。同步与清理逻辑需根据业务严格设计,这是共享内存正确性的基石。
立即学习“go语言免费学习笔记(深入)”;










