
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明确倡导的最佳实践。










