一、什么是协程(Goroutine)?
简单来说,协程是由 Go 语言运行时管理的轻量级线程。相比系统线程,它的调度开销极小,内存占用非常少(默认只需 2KB 栈空间)。
你可以在一个程序中轻松创建成千上万个 goroutine,而不会像传统线程那样造成系统负担。
二、如何创建一个协程
只需要在函数调用前加上 go 关键字,Go 就会在新的协程中异步执行该函数:
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from goroutine")
}
func main() {
go sayHello() // 启动一个新的协程
time.Sleep(1 * time.Second) // 给协程执行的时间
}
如果不加 time.Sleep
,主线程可能直接退出,协程还没执行完,即不会输出"Hello from goroutine"。
三、多个协程并发执行
我们可以轻松开启多个任务同时运行:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 5; i++ {
go func(i int) {
fmt.Printf("Worker %d is running\n", i)
}(i)
}
time.Sleep(1 * time.Second)
}
输出顺序是不确定的,因为每个协程的调度是由 Go 运行时决定的。
四、协程与主线程的关系
主函数是 Go 程序的入口,也是主协程。一旦 main()
执行完毕,程序就退出,即使其他协程还在执行。
为了解决这个问题,我们常用 sync.WaitGroup
等机制来等待所有协程结束:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func worker(id int) {
defer wg.Done()
fmt.Printf("Worker %d done\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i)
}
wg.Wait() // 等待所有 goroutine 完成
}
五、goroutine 的注意事项
闭包中的变量捕获问题
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(3)
for i := 1; i <= 3; i++ {
go func() {
time.Sleep(time.Second)
fmt.Println(i)
wg.Done()
}()
}
wg.Wait() // 等待所有 goroutine 完成
}
正确做法是将变量作为参数传进去:
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(3)
for i := 1; i <= 3; i++ {
go func(val int) {
time.Sleep(time.Second)
fmt.Println(val)
wg.Done()
}(i)
}
wg.Wait() // 等待所有 goroutine 完成
}