必须显式调用mm.flush()才能落盘,因mmap-go默认map_shared仅驻留page cache,defer mm.close()不刷盘,断电或崩溃会导致数据丢失。

为什么 mmap.Open("file") 后改了字节却没写入磁盘
因为 mmap-go 默认用 MAP_SHARED,修改会反映到文件,但仅驻留在 page cache,不自动刷盘。断电或崩溃时未刷数据就丢了。
必须显式调用 mm.Flush() —— 它等价于 msync(MS_SYNC),不是可选项,是强依赖。
-
defer mm.Close()只做munmap,不触发刷盘 - 若用
mmap.RDONLY打开,Flush()会直接返回错误(不可写) - 频繁小写入别每改一次都
Flush(),攒批后统一刷,否则 I/O 开销反超WriteAt - 关键数据落盘前务必
Flush(),尤其在程序退出、服务 reload 前
映射时 panic: "invalid argument" 的三个硬性前提
这不是 Go 代码逻辑错,而是系统调用参数和文件状态不匹配。最常漏检的是以下三点:
- 文件必须已存在且长度 ≥ 映射长度:先
f.Stat()拿Size(),别硬写1024 * 1024 * 1024 - 打开模式必须匹配
prot:只读映射用os.Open+mmap.RDONLY;写入必须用os.OpenFile(f, os.O_RDWR, 0)+mmap.RDWR - Windows 下完全不能用
syscall.Mmap:它只是 stub,返回ENOSYS;跨平台请直接上github.com/edsrzf/mmap-go
用 mmap-go 可跳过页对齐、flag 组合等细节,但它不会帮你校验文件大小——这步你得自己做。
怎么安全地把 mmap 区域当切片用而不 crash
mmap-go 的 mm.Bytes() 返回的切片已正确设置 len 和 cap,可直接下标访问,但有严格限制:
- 禁止
append():底层内存非 Go heap 分配,append会尝试扩容并拷贝,必然 panic - 禁止重切超出范围:如
data[1000:2000]超出映射长度,运行时可能不报错,但访问时触发SIGBUS - 别用
len(data)判断边界:应优先用mm.Len()(mmap-go提供),它返回真实映射长度 - Go 1.21+ 推荐偏移访问方式:
unsafe.Slice(unsafe.Add(unsafe.Pointer(&data[0]), offset), length),但前提是offset + length
什么场景该用 mmap,什么场景该绕开
mmap 不是“只要大文件就上”,性能收益高度依赖访问模式:
- 适合:频繁跳转访问固定偏移(如日志按行号查、数据库索引定位)、多进程共享读写同一文件、需要低延迟随机读
- 不适合:顺序遍历整个 50GB 文件(缺页中断密集,
bufio.NewReader(f).Read更稳)、小文件(mmap 系统调用开销反超普通 IO) - 危险区:映射 >100GB 文件可能撞内核
/proc/sys/vm/max_map_count,报cannot allocate memory,需提前sysctl -w vm.max_map_count=262144 - 真正麻烦的是:mmap 内存不受 GC 管理,
Unmap()必须手动调,且只能对原始切片调一次——子切片、copy 出来的副本都不能传给Unmap
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











