
go程序中启动 goroutine 后,主协程不会自动等待其完成;若未显式同步(如使用 sync.waitgroup 或 channel),主函数可能在 goroutine 执行前就已退出,导致预期的副作用(如切片填充)未生效。
在 Go 中,go 关键字启动的是 非阻塞 异步协程 :主 goroutine(即 main 函数)会立即继续执行后续语句,而不会暂停等待新协程结束。这正是原代码输出空切片的根本原因——fmt.Println(b1, b2) 在 fill 函数尚未运行或仅部分运行时就被执行了,随后程序可能直接退出(即使有 fmt.Scanln,也属于不可靠的“碰运气”式等待)。
要确保主协程等待所有工作 goroutine 完成,推荐使用 sync.WaitGroup —— 它专为这类“等待一组 goroutine 结束”的场景设计。关键步骤有三:
- 创建 WaitGroup 实例;
- 启动 goroutine 前调用 wg.Add(n) 声明需等待的 goroutine 数量;
- 每个 goroutine 结尾调用 wg.Done() 表明自身已完成;
- 主 goroutine 中调用 wg.Wait() 阻塞直到所有计数归零。
以下是修正后的完整可运行代码:
package main import ("fmt" "math/rand" "sync" "time") var (b1 []float64 b2 []float64) func main() { // 初始化随机数种子(否则每次运行结果相同)rand.Seed(time.Now().UnixNano()) var wg sync.WaitGroup wg.Add(2) // 声明将等待 2 个 goroutine go fill(&b1, 10, &wg) go fill(&b2, 10, &wg) wg.Wait() // 主 goroutine 在此阻塞,直至两个 fill 完成 fmt.Println("b1:", b1) fmt.Println("b2:", b2) } func fill(a *[]float64, n int, wg *sync.WaitGroup) {defer wg.Done() // 确保无论何种路径退出,都调用 Done() for i := 0; i < n; i++ { *a = append(*a, rand.Float64()*100) } }
⚠️ 注意事项:
立即学习“go 语言免费学习笔记(深入)”;
- 必须导入 "math/rand" 和 "time":原代码遗漏了 rand.Float64()所需的包,且未设置随机种子,会导致重复结果;
- wg.Add() 必须在 go 语句之前调用:避免竞态(如 Add 和 Done 顺序错乱);
- 建议用 defer wg.Done():比裸写 wg.Done()更安全,能保证即使函数中途 panic 也执行清理;
- 避免依赖 fmt.Scanln 等 I / O 作为同步手段:它不可靠、不直观,且掩盖了并发控制的本质问题。
? 进阶建议:从设计角度看,修改传入切片指针的方式虽可行,但更符合 Go 惯用法的是 返回新切片(类似append 语义),既避免共享状态,又提升函数纯度与可测试性:
func fill(src []float64, n int) []float64 { for i := 0; i < n; i++ { src = append(src, rand.Float64()*100) } return src } // 调用方式:b1 = fill(b1, 10)
这种值传递风格更安全、更易推理,也是 Go 官方 Code Review Comments 明确倡导的最佳实践。






























