
go 程序中启动 goroutine 后,若未显式等待其完成,main 函数结束会导致整个进程立即终止,所有正在运行的 goroutine(包括 db 查询)被强制中断,且不报错——这是并发迁移脚本“无声失败”的根本原因。
go 程序中启动 goroutine 后,若未显式等待其完成,main 函数结束会导致整个进程立即终止,所有正在运行的 goroutine(包括 db 查询)被强制中断,且不报错——这是并发迁移脚本“无声失败”的根本原因。
在 Go 并发编程中,一个常见但极易被忽视的陷阱是:goroutine 是非阻塞、异步执行的,而 main 函数的退出即代表整个程序生命周期的终结。无论后台 goroutine 是否仍在处理数据库查询、解析数据或写入目标库,一旦 main 返回,运行时会立即终止所有协程,且不会触发 panic、不会打印错误、甚至 defer 语句(如 rows.Close())也可能无法执行——这正是你观察到“无输出、无错误、goroutine 数量不稳定”的直接原因。
要正确协调多个 goroutine 的生命周期,必须使用同步原语。sync.WaitGroup 是最标准、最轻量的解决方案:它通过计数器跟踪待完成的任务数量,并提供 Add、Done 和 Wait 三个核心方法,确保主线程阻塞至所有子任务完成。
以下是修复后的关键代码段(已优化为无全局变量、闭包安全、资源可控的写法):
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
import (
"sync"
// ... 其他导入
)
func main() {
// 数据库初始化、total/loops 计算等逻辑保持不变
// ...
fmt.Println("Total:", total)
fmt.Println("Loops:", loops)
var wg sync.WaitGroup
wg.Add(loops) // 预设需等待的 goroutine 总数
for i := 0; i <p>⚠️ <strong>关键注意事项</strong>:</p>
-
闭包陷阱:原始代码中
go processBatch(offset)直接引用循环变量offset,由于 goroutine 启动存在延迟,多个 goroutine 可能共享同一个offset值(最终值)。修复方式是将offset作为参数传入匿名函数,确保每个 goroutine 持有独立副本。 -
WaitGroup 使用规范:
wg.Add()必须在 goroutine 启动前调用;wg.Done()必须在每个 goroutine 内部调用(推荐用defer保证执行);wg.Wait()必须在所有go语句之后、程序退出前调用。 -
数据库连接池安全:
*sql.DB本身是并发安全的,无需额外加锁。但需确保legacyDB和v2DB的MaxOpenConns设置合理(例如db.SetMaxOpenConns(50)),避免因并发过高导致连接耗尽或 MySQL 报Too many connections错误。 -
错误处理增强建议:当前
checkErr使用panic不适合生产环境。建议改为记录日志 + 原子计数失败批次,或通过 channel 收集错误,便于统一诊断。
总结:Go 的并发模型强调“明确同步”,不存在隐式的“等待所有 goroutine 完成”。任何依赖后台 goroutine 完成关键任务(如数据迁移、文件写入、HTTP 请求)的程序,都必须主动使用 WaitGroup、channel 或 context 进行协调。忽略这一点,程序看似简洁,实则脆弱不可靠。










