
go 程序中启动 goroutine 执行数据库批量迁移任务后立即退出,是因为 main 函数返回导致整个进程终止,而未等待 goroutine 完成;正确做法是使用 sync.waitgroup 同步等待所有 goroutine 结束。
go 程序中启动 goroutine 执行数据库批量迁移任务后立即退出,是因为 main 函数返回导致整个进程终止,而未等待 goroutine 完成;正确做法是使用 sync.waitgroup 同步等待所有 goroutine 结束。
在 Go 中,goroutine 是轻量级并发单元,但其生命周期不绑定于主 goroutine(即 main 函数)。一旦 main 函数执行完毕并返回,整个程序立即退出——无论其他 goroutine 是否仍在运行、是否已开始执行或是否卡在数据库调用中。这正是你遇到问题的根本原因:go processBatch(offset) 启动了多个 goroutine,但 main 函数紧接着就执行到 legacyDB.Close() 和 v2DB.Close() 并结束,导致所有尚未真正进入 processBatch 执行逻辑的 goroutine 被强制终止(甚至可能还未调度),因此日志无输出、数据无写入、也无显式错误(panic 或 error 被截断)。
要解决该问题,必须显式协调主 goroutine 与其他 goroutine 的生命周期。sync.WaitGroup 是标准且最合适的工具:它通过计数器跟踪待完成的 goroutine 数量,并提供 Add、Done 和 Wait 三个核心方法实现同步等待。
以下是修正后的关键代码段(已整合进完整流程):
import (
"sync"
// ... 其他导入
)
func main() {
// 数据库初始化、total 计算等逻辑保持不变
// ...
fmt.Println("Total:", total)
fmt.Println("Loops:", loops)
var wg sync.WaitGroup
wg.Add(loops) // 声明需等待 loops 个 goroutine
for i := 0; i <p>⚠️ <strong>关键注意事项</strong>:</p>
-
闭包变量捕获陷阱:若直接在 goroutine 中使用
i或offset(未作为参数传入),由于 for 循环快速迭代,所有 goroutine 可能共享同一个变量地址,最终都读取到循环结束后的值(如offset = loops * batchsize)。务必通过函数参数显式传递当前值。 -
wg.Done()必须被调用:建议始终用defer wg.Done()包裹,确保即使processBatch发生 panic 或提前 return,计数器也能正确递减。 -
数据库连接池已就绪:
sql.DB本身是并发安全的,支持多 goroutine 同时调用Query/Exec,无需额外加锁;但需确保legacyDB和v2DB在wg.Wait()之后才调用Close(),否则可能中断正在进行的查询。 -
错误处理增强建议:当前
checkErr使用panic,在并发场景下会导致单个 goroutine 崩溃但不影响其他任务。更健壮的做法是将错误通过 channel 汇总,或在processBatch内部记录日志并继续执行。
总结:Go 的并发模型强调“不要通过共享内存来通信,而应通过通信来共享内存”,但在生命周期管理上,WaitGroup 提供了一种简洁、零分配、无锁的同步原语。掌握它,是编写可靠并发 Go 程序的第一道必修课。











