golang对共享内存的操作

 不同进程间的内存是互相独立的,没办法直接互相操作对方内的数据,而共享内存则是靠操作系统提供的内存映射机制,让不同进程的一块地址空间映射到同一个虚拟内存区域上,使不同的进程可以操作到一块共用的内存块。共享内存是效率最高的进程间通讯机制,因为数据不需要在内核和程序之间复制。

共享内存用到的是系统提供的mmap函数,它可以将一个文件映射到虚拟内存的一个区域中,程序使用指针引用这个区域,对这个内存区域的操作会被回写到文件上,其函数原型如下:

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
  • 参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。
  • len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
  • prot参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行),PROT_NONE(不可访问)。
  • flags由以下几个常值指定:MAP_SHARED, MAP_PRIVATE, MAP_FIXED。其中,MAP_SHARED,MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。 如果指定为MAP_SHARED,则对映射的内存所做的修改同样影响到文件。如果是MAP_PRIVATE,则对映射的内存所做的修改仅对该进程可见,对文件没有影响。
  • offset参数一般设为0,表示从文件头开始映射。
  • 参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。

顺带介绍一下shm_open和shm_unlink两个函数:

shm_open()函数
功能:    打开或创建一个共享内存区
头文件:    #include <sys/mman.h>
函数原形:    int shm_open(const char *name,int oflag,mode_t mode);
返回值:    成功返回0,出错返回-1
参数:    
    name    共享内存区的名字
    oflag    标志位
    mode    权限位
参数解释:oflag参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC.mode参数指定权限位,
它指定O_CREAT标志的前提下使用。shm_open的返回值是一个整数描述字,它随后用作mmap的第五个参数。

shm_unlink()函数
功能:    删除一个共享内存区
头文件:    #include <sys/mman.h>
函数原形:    int shm_unlink(const char *name);
参数:     name    共享内存区的名字
返回值:    成功返回0,出错返回-1
shm_unlink函数删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的open,mq_open或sem_open调用取得成功。

可以参考此文章的介绍来进一步了解mmap等函数:http://www.cnblogs.com/polestar/archive/2012/04/23/2466022.html

可以利用golang调用cgo的方法实现c中的mmap。实验分为读和写两个程序,这样我们可以观察到读进程可以读到写进程写入共享内存的信息。

shm_writer.go代码示例:

package main

/*
#cgo linux LDFLAGS: -lrt

#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#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

type MyData struct {
    Col1 int
    Col2 int
    Col3 int
}

func main() {
    fd, err := C.my_shm_new(C.CString(SHM_NAME))
    if err != nil {
        fmt.Println(err)
        return
    }

    C.ftruncate(fd, SHM_SIZE)

    ptr, err := C.mmap(nil, SHM_SIZE, C.PROT_READ|C.PROT_WRITE, C.MAP_SHARED, fd, 0)
    if err != nil {
        fmt.Println(err)
        return
    }
    C.close(fd)

    data := (*MyData)(unsafe.Pointer(ptr))

    data.Col1 = 100
    data.Col2 = 876
    data.Col3 = 8021
}
shm_reader.go代码示例:
package main

/*
#cgo linux LDFLAGS: -lrt

#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

int my_shm_open(char *name) {
    return shm_open(name, O_RDWR, FILE_MODE);
}
*/
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() {
    fd, err := C.my_shm_open(C.CString(SHM_NAME))
    if err != nil {
        fmt.Println(err)
        return
    }

    ptr, err := C.mmap(nil, SHM_SIZE, C.PROT_READ|C.PROT_WRITE, C.MAP_SHARED, fd, 0)
    if err != nil {
        fmt.Println(err)
        return
    }
    C.close(fd)

    data := (*MyData)(unsafe.Pointer(ptr))

    fmt.Println(data)
}

上面的程序映射了一块4G的虚拟内存,用来证明mmap没有实际占用4G内存,而是用到了虚拟内存。shm_writer创建好共享内存以后,往内存区域写入了一个结构体,shm_reader则读出一个结构体。

上面代码中还用到一个cgo的技巧,像shm_open和mmap函数在错误时会返回errno,如果我们在go中使用多返回值语法,cgo会自己把错误码转换成错误信息,很方便的功能。

转载于:https://my.oschina.net/renguijiayi/blog/179189

### Golang 中通过通信共享内存的方法 在 Go 语言中,`channel` 是 goroutine 之间的主要通信方式。这种设计遵循了 CSP(Communicating Sequential Processes)模型的理念:不是通过共享内存来进行通信;而是通过通信来共享内存[^1]。 下面是一个简单的例子,展示如何使用 `channel` 来实现两个 goroutine 之间的数据传递: ```go package main import ( "fmt" ) func sendData(ch chan int) { for i := 0; i < 5; i++ { ch <- i // 向通道发送数据 fmt.Printf("Sent %d\n", i) } close(ch) // 关闭通道表示不再发送更多数据 } func receiveData(ch chan int) { for num := range ch { // 接收来自通道的数据直到它被关闭 fmt.Printf("Received %d\n", num) } } func main() { channel := make(chan int) // 创建一个新的整型通道 go sendData(channel) // 启动一个goroutine用于发送数据 receiveData(channel) // 主线程负责接收并处理这些数据 } ``` 在这个实例里,`sendData()` 函数作为生产者不断向通道 `ch` 写入数值,而 `receiveData()` 则扮演消费者的角色从同一通道读取消息。当所有的消息都被发出之后,会调用 `close()` 方法通知接收方已经没有更多的输入项了。 需要注意的是,在上述代码片段中的 `main()` 函数启动了一个新的 goroutine 执行 `sendData()` 操作,这使得两者可以在不阻塞彼此的情况下异步运行。一旦所有要传送的信息都已准备好,则由主线程完成剩余的任务——即收集并通过控制台打印出来自其他协程的结果。 尽管这里讨论的主题是关于 Goroutines 和 Channels 如何协作以达到“通过通信共享内存”的效果,但值得注意的是,Go 并未提供直接支持跨进程间共享内存的功能。对于后者的需求,通常需要借助于操作系统层面的技术如 POSIX 共享内存段或是 mmap 文件映射等方式实现[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值