优先使用github.com/edsrzf/mmap-go而非syscall.mmap,因其自动处理页对齐、跨平台映射、错误转换、len/cap校验及并发安全,避免sigbus、panic和windows崩溃;syscall.mmap需手动处理平台差异、权限匹配、文件大小校验及msync刷盘,极易出错。

别用 syscall.Mmap 手撸,直接上 github.com/edsrzf/mmap-go —— 它能避开 90% 的 SIGBUS、panic 和跨平台崩溃。
为什么 syscall.Mmap 在 Go 里基本等于自找麻烦
Linux/macOS 要对齐页边界(4096 字节)、校验 prot/flags 组合合法性;Windows 根本不支持 syscall.Mmap(它只是个返回 ENOSYS 的 stub),得拼 CreateFileMapping + MapViewOfFile;映射后还得手动构造 reflect.SliceHeader 或用 unsafe.Slice,稍一越界就 SIGBUS。常见报错 syscall.Mmap: invalid argument 其实和 Go 代码无关,而是:
– 文件长度为 0 或小于映射长度
– 用 os.Open(只读)却传 syscall.PROT_WRITE
– Windows 下权限不匹配直接失败,Linux 下却可能静默降级,行为不一致
mmap-go 怎么安全打开并读写已有文件
这是最常用场景:加载大日志、索引或二进制数据,避免 os.ReadFile 一次性吃光内存。
- 只读映射:用
os.Open+mmap.RDONLY,macOS 还有额外优化 - 读写映射:必须用
os.OpenFile(name, os.O_RDWR, 0)+mmap.RDWR,否则写操作会触发SIGBUS - 映射前务必调
f.Stat()拿真实大小,别信配置项或常量;若需扩容,先syscall.Ftruncate(int(f.Fd()), newSize) -
mm.Bytes()返回的切片已设好len和cap,可直接下标访问,但禁止append或重切越界(如data[1000:2000]超出mm.Len())
file, _ := os.OpenFile("data.bin", os.O_RDWR, 0)
defer file.Close()
fi, _ := file.Stat()
mm, _ := mmap.Map(file, mmap.RDWR, 0)
defer mm.Unmap()
data := mm.Bytes()
data[0] = 0xff
mm.Flush() // 关键:不调这个,断电就丢数据
写入后数据为什么没落盘?Flush() 不是可选项
mmap-go 默认用 MAP_SHARED,修改会反映到 page cache,但内核不自动刷盘。程序退出、崩溃或断电时,未 Flush() 的数据就丢了。
-
defer mm.Unmap()或mm.Close()只做解映射,不触发同步 -
mm.Flush()等价于msync(MS_SYNC),是强依赖步骤 - 若用
mmap.RDONLY打开,Flush()直接返回错误(不可写) - 频繁小写入别每改一次都
Flush(),攒批后统一刷,否则 I/O 开销反超WriteAt
多进程同时 mmap 同一文件写入会怎样
会错乱,除非加锁。用 MAP_SHARED(mmap-go 默认)时,多个进程看到的是同一份页缓存,但写操作不是原子的——两个进程同时改同一偏移,结果取决于 CPU 指令交错顺序,大概率丢数据。
- 单进程内多 goroutine 并发读写没问题(
mmap-go内部已加锁管理Unmap) - 跨进程写必须自己加锁,比如用文件锁(
flock)或共享内存信号量 - 映射时文件被其他进程修改,Go 程序会看到,但不是实时、不是原子、不保证顺序
最容易被忽略的是:映射长度必须 ≤ 文件当前大小,且 Flush() 必须显式调用——这两点不满足,程序可能跑几天都没问题,直到某次断电或 reload,数据就永远消失了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











