题目
如何创建一个Goroutine并等待其完成?
信息
- 类型:问答
- 难度:⭐
考点
Goroutine创建, sync.WaitGroup使用, 并发控制
快速回答
使用Go关键字创建Goroutine,配合sync.WaitGroup实现同步:
- 声明
sync.WaitGroup变量 - 启动Goroutine前调用
wg.Add(1) - 在Goroutine内部使用
defer wg.Done() - 主函数中调用
wg.Wait()等待结束
原理说明
Goroutine是Go语言的轻量级线程,由Go运行时管理。使用sync.WaitGroup可解决主协程提前退出导致子协程未完成的问题,它通过计数器实现协程同步:
Add(n):增加计数器值Done():计数器减1(等价于Add(-1))Wait():阻塞直到计数器归零
代码示例
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // 计数器+1
go worker(i, &wg) // 注意传递指针
}
wg.Wait() // 等待所有worker完成
fmt.Println("所有任务完成")
}
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 确保函数退出时执行
fmt.Printf("Worker %d 开始工作\n", id)
time.Sleep(time.Second) // 模拟耗时任务
fmt.Printf("Worker %d 完成工作\n", id)
}最佳实践
- 传递WaitGroup指针:避免值拷贝导致计数器失效
- 使用defer调用Done():确保即使发生panic也能释放计数器
- Add在Goroutine外调用:防止竞态条件
- 控制并发数:避免无限制创建Goroutine导致资源耗尽
常见错误
- 忘记Add:导致
Wait()立即返回,协程未执行完 - 传递值而非指针:
wg拷贝后计数器不共享 - Done()调用次数不足:
Wait()永久阻塞 - 在Goroutine内调用Add:可能主协程先执行
Wait()
扩展知识
- Goroutine泄漏:未正确退出会导致内存泄漏,可用
runtime.NumGoroutine()检测 - 替代方案:
errgroup.Group(支持错误传递)、context.Context(超时控制) - 调度机制:GMP模型(Goroutine-Machine-Processor)实现高效调度
- 并发vs并行:Goroutine是并发单元,Go运行时自动利用多核实现并行