go本身不支持热重载,所有工具均通过“监听文件→杀旧进程→编译新二进制→启动新进程”实现;本质是开发期自动化补丁,非语言特性,需严格对齐root、build.bin与build.cmd路径及[watch]/[build]两节include_ext配置。

Go 本身不支持热重载,所有“热重载”工具实际都是「监听文件 → 杀旧进程 → 编译新二进制 → 启动新进程」的自动化流程。它不是语言特性,而是开发阶段的效率补丁,用对了能省下每天几十次 Ctrl+C + go run 的时间,用错了反而卡在 fork/exec ./tmp/main: permission denied 或改了没反应。
为什么 go run 不能当热重载用
go run 每次执行都新建一个进程,不保留 PID、不管理生命周期、不监听文件变更。你保存后它不会自己动,必须手动中断再敲一遍命令——这不是热重载,是手动轮询。
- 它不释放旧端口:
go run崩溃或被 Ctrl+C 中断后,http.Server若没调Shutdown(),端口会卡住,下次go run直接报address already in use - 它不处理信号:
os.Interrupt和syscall.SIGTERM默认不注册,air 或 realize 发 SIGTERM 杀进程时,旧实例可能僵死成僵尸态 - 它不区分构建与运行:无法插入
-gcflags='all=-N -l'这类调试必需参数,Delve attach 会失败
air 配置里最容易踩的三个硬坑
90% 的 air 启动失败或静默不生效,问题不在工具本身,而在三处路径/命令没对齐:
-
root必须指向含go.mod的目录(通常是项目根),不是main.go所在目录;设错会导致go build找不到 module -
build.bin和build.cmd输出路径必须严格一致,例如都用./tmp/main;不一致会报exec: "./tmp/main": file does not exist -
[watch]和[build]两节的include_ext必须完全相同;只在 watch 里加yaml,但 build 不认,改配置文件就不会触发重建
改了 internal/handler/xxx.go 没反应?检查监听范围
air 默认只监听当前工作目录及子目录下的 .go 文件,不跨模块、不跟进符号链接、不扫描 vendor/ 或隐藏目录。如果你的 main.go 在 cmd/api/main.go,却在项目根目录运行 air,那 internal/ 下的改动根本不会被看到。
- 正确做法:cd 到
cmd/api/目录下运行air,或在.air.toml中显式配置watch.cwd = "cmd/api" - 多 main 项目(如
cmd/api和cmd/worker)必须指定main_cmd = "go run cmd/api/main.go",否则 air 默认找main.go会错位 - Windows 用户注意:路径一律用正斜杠
/,哪怕在 PowerShell 里写.\tmp\main也会导致监听失效
容器内热重载必须配 bind mount + air 一起用
单纯在宿主机跑 air 再 docker build 是伪热重载,镜像层没变,容器内代码还是旧的。真正在容器里热重载,得让代码实时透传进去:
- Docker Compose 中挂载源码:
volumes: ["./:/app"],确保容器内路径和本地一致 - 容器启动后直接运行
air(不是go run),让它在容器内监听/app目录 - 关键:构建命令要适配容器环境,
build.cmd = "go build -o /app/tmp/main .",输出路径必须可写且在挂载卷内 - 别忘了
tmp_dir = "/app/tmp"并加进.gitignore,否则 git 会误提交临时二进制
真正难的不是让代码动起来,而是让进程优雅退出、端口及时释放、调试器能稳定 attach——这些细节漏掉一个,热重载就退化成更难 debug 的随机失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











