goroutine生命周期管理
目录
在 Go 语言中,goroutine 是一种轻量级的线程,由 Go 运行时(runtime)管理。goroutine 的生命周期从创建开始,到任务完成或主动退出结束。由于 goroutine 是并发执行的,管理它们的生命周期是编写高效、可靠并发程序的关键。
以下是 goroutine 生命周期的管理方法和注意事项:
1. 创建 goroutine
使用 go 关键字可以创建一个 goroutine。goroutine 会立即开始执行指定的函数。
go func() {
fmt.Println("This is a goroutine")
}()
- goroutine 的创建成本很低,通常只需要几 KB 的栈空间(栈会根据需要动态增长)。
- goroutine 的调度由 Go 运行时负责,开发者无需手动管理线程。
2. goroutine 的生命周期
goroutine 的生命周期从 go 语句开始,到以下情况之一结束:
- 函数执行完毕。
- 函数中调用了
return。 - 函数中发生了未被恢复的 panic。
- 程序退出(主 goroutine 退出时,所有 goroutine 都会被强制终止)。
3. 管理 goroutine 的生命周期
由于 goroutine 是并发执行的,如果不加以管理,可能会导致以下问题:
- 资源泄漏:goroutine 未正确退出,导致资源无法释放。
- 数据竞争:多个 goroutine 同时访问共享资源,导致数据不一致。
- 主 goroutine 提前退出:主 goroutine 退出后,其他 goroutine 会被强制终止,可能导致任务未完成。
以下是管理 goroutine 生命周期的常用方法:
3.1 使用 sync.WaitGroup 等待 goroutine 完成
sync.WaitGroup 是一种同步机制,用于等待一组 goroutine 完成任务。
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 任务完成时调用 Done
fmt.Printf("Worker %d starting\n", id)
// 模拟任务
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // 增加计数器
go worker(i, &wg)
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println("All workers done")
}
wg.Add(1):增加计数器,表示启动一个 goroutine。wg.Done():减少计数器,表示一个 goroutine 完成任务。wg.Wait():阻塞主 goroutine,直到计数器归零。
3.2 使用 context 控制 goroutine 的取消
context 包提供了一种机制,用于在 goroutine 之间传递取消信号、超时和截止时间。
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done(): // 收到取消信号
fmt.Printf("Worker %d canceled: %v\n", id, ctx.Err())
return
default:
fmt.Printf("Worker %d is working\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
for i := 1; i <= 3; i++ {
go worker(ctx, i)
}
time.Sleep(2 * time.Second)
cancel() // 取消所有 goroutine
time.Sleep(1 * time.Second) // 给 goroutine 一些时间退出
fmt.Println("Main goroutine done")
}
context.WithCancel:创建一个可取消的 context。ctx.Done():返回一个 channel,当 context 被取消时会关闭该 channel。cancel():发送取消信号,通知所有监听该 context 的 goroutine 退出。
3.3 使用 channel 控制 goroutine 的退出
channel 是 goroutine 之间通信的主要方式,也可以用于控制 goroutine 的退出。
package main
import (
"fmt"
"time"
)
func worker(stopChan chan bool) {
for {
select {
case <-stopChan: // 收到退出信号
fmt.Println("Worker exiting")
return
default:
fmt.Println("Worker is working")
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
stopChan := make(chan bool)
go worker(stopChan)
time.Sleep(2 * time.Second)
stopChan <- true // 发送退出信号
time.Sleep(1 * time.Second) // 给 goroutine 一些时间退出
fmt.Println("Main goroutine done")
}
- 通过 channel 发送退出信号,goroutine 监听该信号并退出。
3.4 避免 goroutine 泄漏
goroutine 泄漏是指 goroutine 无法退出,导致资源无法释放。以下是一些避免泄漏的建议:
- 确保 goroutine 有明确的退出条件。
- 使用
context或 channel 控制 goroutine 的退出。 - 避免在 goroutine 中无限循环,除非有明确的退出机制。
4. 主 goroutine 的管理
主 goroutine 是程序的入口,它的退出会导致整个程序退出。因此,主 goroutine 需要等待其他 goroutine 完成任务后再退出。
- 使用
sync.WaitGroup或context确保主 goroutine 等待其他 goroutine 完成。 - 避免在主 goroutine 中直接调用
os.Exit,这会导致其他 goroutine 被强制终止。
5. 总结
- goroutine 的生命周期从创建开始,到任务完成或退出结束。
- 使用
sync.WaitGroup、context或 channel 管理 goroutine 的生命周期。 - 避免 goroutine 泄漏,确保 goroutine 有明确的退出条件。
- 主 goroutine 需要等待其他 goroutine 完成任务后再退出。
通过合理管理 goroutine 的生命周期,可以编写出高效、可靠的并发程序。
Do not communicate by sharing memory; instead, share memory by communicating.

浙公网安备 33010602011771号