应优先使用 github.com/edsrzf/mmap-go 库,因其自动处理跨平台兼容性、页对齐、错误码转换及 len/cap 设置;直接调用 syscall.Mmap 易因权限不匹配、未对齐、Windows 不支持等问题导致 SIGBUS、内存泄漏或零容量崩溃。

为什么 mmap 在 Go 里不能直接用 syscall.Mmap
Go 标准库没有暴露 syscall.Mmap 的跨平台封装,它在不同系统上签名和行为差异大(比如 Windows 不支持 MAP_ANONYMOUS,Linux 需要 PROT_READ/PROT_WRITE 组合),直接调用容易 panic 或映射失败。官方明确不推荐用户直用底层 syscall,而是通过 mmap 第三方包或 os.File.ReadAt/WriteAt 配合预分配来绕过。
用 github.com/edsrzf/mmap-go 实现读写最简路径
这个包是事实标准,覆盖 Linux/macOS/Windows,API 稳定。关键不是“能不能映射”,而是“怎么避免常见崩溃”:
-
mmap.MapRegion返回的[]byte是可读写的内存视图,但必须显式调用Unmap(),否则进程退出前不会释放映射——尤其在循环处理多个文件时,漏调用会触发too many open files错误 - 映射大小必须 ≤ 文件实际长度;若想扩展文件,得先用
file.Truncate(),再重新mmap.MapRegion - 写入后需调用
msync(即mm.Sync())确保落盘,否则程序崩溃时修改可能丢失——mm.Flush()只刷新到内核页缓存,不等同于 fsync
示例:读取 2GB 文件第 1GB 处的 4KB 数据
mm, err := mmap.MapRegion(file, 1<<30, mmap.RDONLY, 0, 0)
if err != nil {
log.Fatal(err)
}
defer mm.Unmap() // 必须
data := mm[0:4096]
什么时候不该用 mmap
小文件(
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- Go runtime 的 GC 会扫描所有映射内存,大映射区拖慢 GC 周期;若只读部分数据,用
io.CopyN+bytes.Buffer更轻量 - 多个 goroutine 写同一 mmap 区域需自己加锁,
sync.Mutex比atomic开销大,不如用file.WriteAt分块写 - Windows 上
CreateFileMapping对稀疏文件支持弱,映射后len(mm)可能小于预期,需配合file.Stat().Size()校验
替代方案:os.File.ReadAt + 预分配切片
对大多数“快速读写大文件”需求,其实不需要 mmap——Go 的 ReadAt 已经绕过用户态拷贝,性能接近 mmap,且无生命周期管理负担:
- 提前用
make([]byte, size)分配缓冲区,避免频繁 malloc - 读写偏移用
file.ReadAt(buf, offset),比file.Seek + Read少一次系统调用 - 写完调
file.Sync()保证持久化,比mm.Sync()语义更清晰
真正卡点不在“怎么快”,而在“是否真需要 mmap 级别的零拷贝”。多数日志解析、二进制协议解析场景,ReadAt 足够,且代码更易测、更少出错。

















