Go语言通过CGO实现共享内存读写实例

咖啡半糖
咖啡半糖
发布时间:2026-10-14 | 4人浏览过
原创

共享内存是进程间通信(IPC)中效率最高的方式,因为它避免了数据在内核态与用户态之间的复制。在Go语言中,虽然标准库提供了基础的文件和网络操作,但要实现底层高效的共享内存映射,通常需要借助CGO调用系统的mmap接口。本文将通过分析具体的代码实例,演示如何在Linux环境下使用Go语言和CGO配合完成共享内存的创建、映射、写入与读取。

共享内存原理与CGO选型

不同进程间的内存空间默认是相互独立的,无法直接访问对方的数据。共享内存技术利用操作系统的内存映射机制,将同一块物理内存映射到多个进程的虚拟地址空间中,从而实现数据的实时共享。
Go语言内置的syscall包虽然包含mmap封装,但其返回类型为[]byte,不支持指针运算,难以满足对结构体进行直接字段操作的场景。因此,本例采用CGO直接调用C语言原生的mmap及相关系统调用,以便通过指针直接操作内存中的结构体数据。

准备工作

实验环境为Linux系统。由于mmap及共享内存相关的函数(如shm_open, shm_unlink)属于POSIX标准,需要链接librt库。在使用CGO时,必须正确配置LDFLAGS以确保编译通过。

第1步:编写共享内存写入端 (shm_writer.go)

写入端负责创建命名共享内存对象、设置大小,并将一个自定义结构体的数据写入该内存区域。

package main

/*
#cgo linux LDFLAGS: -lrt
#include 
#include 
#include 
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int my_shm_new(char *name) {
	shm_unlink(name); // 先清理可能存在的旧对象
	return shm_open(name, O_RDWR|O_CREAT|O_EXCL, FILE_MODE);
}
*/
import "C"
import (
	"fmt"
	"unsafe"
)

const SHM_NAME = "my_shm"
const SHM_SIZE = 4 * 1000 * 1000 * 1000 // 4GB 虚拟内存

type MyData struct {
	Col1 int
	Col2 int
	Col3 int
}

func main() {
	// 1. 创建共享内存对象
	fd, err := C.my_shm_new(C.CString(SHM_NAME))
	if err != nil {
		fmt.Println("create shm failed:", err)
		return
	}

	// 2. 设定共享内存大小
	C.ftruncate(fd, SHM_SIZE)

	// 3. 映射共享内存到当前进程虚拟地址
	ptr, err := C.mmap(nil, SHM_SIZE, C.PROT_READ|C.PROT_WRITE, C.MAP_SHARED, fd, 0)
	if err != nil {
		fmt.Println("mmap failed:", err)
		return
	}

	// 关闭文件描述符,后续通过指针访问内存即可
	C.close(fd)

	// 4. 通过指针转换写入结构体数据
	data := (*MyData)(unsafe.Pointer(ptr))
	data.Col1 = 100
	data.Col2 = 876
	data.Col3 = 8021

	fmt.Println("Writer done. Data written to shared memory.")
}

关键点解析:
- #cgo linux LDFLAGS: -lrt:这是CGO的条件编译指令。在Linux下编译时必须链接-lrt库以支持shm_open等函数;若在macOS上编译则无需此链接(Mac原生支持且无需额外库),因此通过条件语句区分平台。
- unsafe.Pointer:用于将C语言返回的mmap指针转换为Go的类型指针,从而直接操作内存块。

第2步:编写共享内存读取端 (shm_reader.go)

读取端仅打开已有的共享内存对象并进行映射,然后读取其中的结构体数据。

package main

/*
#cgo linux LDFLAGS: -lrt
#include 
#include 
#include 
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int my_shm_open(char *name) {
	return shm_open(name, O_RDWR);
}
*/
import "C"
import (
	"fmt"
	"unsafe"
)

const SHM_NAME = "my_shm"
const SHM_SIZE = 4 * 1000 * 1000 * 1000

type MyData struct {
	Col1 int
	Col2 int
	Col3 int
}

func main() {
	// 1. 打开已存在的共享内存对象
	fd, err := C.my_shm_open(C.CString(SHM_NAME))
	if err != nil {
		fmt.Println("open shm failed:", err)
		return
	}

	// 2. 映射共享内存
	ptr, err := C.mmap(nil, SHM_SIZE, C.PROT_READ|C.PROT_WRITE, C.MAP_SHARED, fd, 0)
	if err != nil {
		fmt.Println("mmap failed:", err)
		return
	}

	// 关闭文件描述符
	C.close(fd)

	// 3. 读取结构体数据
	data := (*MyData)(unsafe.Pointer(ptr))
	fmt.Printf("Reader read data: %+v\n", data)
}

运行验证与注意事项

在实际测试中,建议先运行 shm_writer.go 再运行 shm_reader.go。可以看到输出结果:
{Col1:100 Col2:876 Col3:8021}
这证明了不同进程可以通过共享名称的内存区域成功交换数据。

关于虚拟内存大小的说明:
代码中设置了 SHM_SIZE = 4GB,但这并不意味着实际占用4GB的物理内存。这只是向操作系统申请了一块连续的虚拟地址空间。物理内存只有在真正被访问(Page Fault)时才会分配。如果试图一次性读写这4GB全零数据,可能会导致性能极差或触发系统保护机制。在生产环境中,应根据实际需求设置合理的大小,并确保系统有足够的虚拟地址空间(通常是64位系统才能支撑大内存映射)。

此外,CGO的多返回值特性在遇到C库错误时会自动转换为Go error类型,极大地简化了错误处理逻辑,这是使用CGO时的一个便捷技巧。

以上就是Go语言通过CGO调用mmap实现共享内存读写的详细内容,更多关于Go语言进程间通信的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表