package main

import (
	"fmt"
	mmap "github.com/edsrzf/mmap-go"
	"os"
	"sync/atomic"
	"time"
	"unsafe"
)

func main() {

	f, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE, 0644)
	// 写入初始内容,前4字节必须是 0x00000000
	buf := make([]byte, 4)                           // 4字节零值锁
	buf = append(buf, []byte("Ciallo~(∠・ω< )⌒★")...) // 实际数据
	f.Write(buf)

	if err != nil {
		panic(err)
	}
	defer f.Close()

	m, err := mmap.Map(f, mmap.RDWR, 0)
	if err != nil {
		panic(err)
	}
	defer m.Unmap()

	for i := 0; i < 25; i++ {
		// 约定 m[0] 为锁标志位,用原子操作
		for !atomic.CompareAndSwapUint32((*uint32)(unsafe.Pointer(&m[0])), 0, 1) {
			time.Sleep(time.Millisecond)
		}
		m[4] = m[4] + byte(1)
		m.Flush()
		fmt.Println(string(m[4:]))
		//解锁
		atomic.StoreUint32((*uint32)(unsafe.Pointer(&m[0])), 0)
		time.Sleep(time.Second)
	}

	time.Sleep(100 * time.Second)
}
package main

import (
	"fmt"
	"os"
	"time"

	mmap "github.com/edsrzf/mmap-go"
)

func main() {

	f, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	m, err := mmap.Map(f, mmap.RDWR, 0)
	if err != nil {
		panic(err)
	}
	defer m.Unmap()

	for {
		// 输出跳过前四个设定为锁标志位的数据
		fmt.Println("读取:", string(m[4:]))
		time.Sleep(time.Second)
	}

}

Go 文件内存映射(mmap)笔记

1. 什么是 mmap

将文件映射到进程的虚拟内存空间,直接用指针/切片操作文件内容。

优势:

不适合场景:


2. Go 中使用 mmap

推荐库(跨平台):

go get github.com/edsrzf/mmap-go

基本用法:

// 只读
f, _ := os.Open("test.txt")
m, _ := mmap.Map(f, mmap.RDONLY, 0)
defer m.Unmap()

// 读写(必须用 OpenFile,不能用 Open)
f, _ := os.OpenFile("test.txt", os.O_RDWR, 0644)
m, _ := mmap.Map(f, mmap.RDWR, 0)
m[0] = 'B'
m.Flush() // 刷新到磁盘

注意事项:


3. 多进程通信(mmap IPC)

多个进程映射同一文件,共享同一块物理内存页。

文件布局约定:

[0:4]   锁标志位(uint32,初始必须是数值 0)
[4:]    实际数据

初始化文件:

buf := make([]byte, 4)                  // 4字节零值锁
buf = append(buf, []byte("实际数据")...)
f.Write(buf)

4. 自旋锁实现(跨平台)

import (
    "sync/atomic"
    "time"
    "unsafe"
)

// 加锁(注意必须有 !)
for !atomic.CompareAndSwapUint32((*uint32)(unsafe.Pointer(&m[0])), 0, 1) {
    time.Sleep(time.Millisecond)
}

// 临界区操作(数据从 m[4] 开始)
m[4] = m[4] + 1
m.Flush()

// 解锁
atomic.StoreUint32((*uint32)(unsafe.Pointer(&m[0])), 0)

CAS 原理:

atomic.CompareAndSwapUint32(addr, old, new)
// if *addr == old { *addr = new; return true }
// else { return false }

加锁:m[0]==0(空闲) → CAS成功返回true → 取反false → 退出循环 → 拿到锁
等待:m[0]==1(已锁) → CAS失败返回false → 取反true → 继续循环等待

常见错误: 忘记 ! 取反,导致拿到锁反而在睡觉,没拿到锁反而进入临界区。


5. 多 main 项目结构

myproject/
├── cmd/
│   ├── writer/
│   │   └── main.go
│   └── reader/
│       └── main.go
└── go.mod
go run ./cmd/writer
go run ./cmd/reader
go build ./cmd/writer

6. Windows 跨进程同步方案对比

方案跨进程复杂度推荐度
自旋锁(标志位 + atomic)★★★
命名互斥量(windows.CreateMutex)★★★★
文件锁(LockFileEx)★★★
unix.Flock否(Linux/macOS 专属)-