Golang 从入门到放弃 -0x0C
goroutine、闭包循环变量坑和 WaitGroup,建立并发编程的第一层直觉。
终于来到 Go 的明星功能:goroutine。很多人学 Go,就是冲着它来的。别急着把它神化,它本质上是 Go 提供的一种轻量级并发执行方式。好用是真的好用,但乱开也是真的会出事。
什么是 goroutine
你可以先把它理解成“由 Go 运行时管理的轻量级任务”。启动方式也非常朴素,在函数调用前面加一个 go 关键字就行。
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("hello from goroutine")
}
func main() {
go sayHello()
time.Sleep(time.Second)
}这里如果你不加那句 time.Sleep,程序可能一闪而过,goroutine 还没来得及执行,主函数就先下班了。记住一句话:main 结束,整个进程就结束。
开很多 goroutine 也不稀奇
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Println("goroutine", n)
}(i)
}
time.Sleep(time.Second)这里顺手提一个常见坑:循环变量别直接闭包捕获,最好像上面这样通过参数传进去。否则你很可能得到一堆重复结果,然后开始怀疑人生。
更靠谱的等待方式:WaitGroup
time.Sleep 只是演示时图省事,真实代码里最好用同步工具来等任务结束。最常见的就是 sync.WaitGroup。
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("worker", id, "started")
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("all done")
}这个套路特别常见:
- 启动前
Add - 任务结束时
Done - 主流程最后
Wait
并发不等于并行
这两个词经常被混着说。粗暴点理解:
- 并发:多件事交替推进
- 并行:多件事同一时刻真在一起跑
Go 很擅长写并发程序,但你也别以为一加 goroutine 性能就自动起飞。任务本身适不适合并发、有没有锁竞争、有没有 I/O 阻塞,这些都会影响结果。
goroutine 也会带来麻烦
别把 goroutine 当不要钱的小广告贴满整个项目。它虽然轻量,但也不是免费的。常见问题包括:
- 主程序提前退出
- 共享数据竞争
- goroutine 泄漏
- 日志顺序混乱,看起来像谁都没写错,但就是不对
所以并发这事,重点不是“会开”,而是“开了以后你还能收得回来”。
小结
这一章先有个初印象就行:
- 在函数前加
go就能启动 goroutine。 main结束,所有 goroutine 一起下班。- 同步等待不要老靠
time.Sleep,更推荐WaitGroup。
下一章我们继续把并发讲完整,轮到 channel 和 select 出场了。Go 真正的“优雅并发宣传片”,差不多就是从那里开始放的。