观测协程是否正常运行
【摘要】 1 简介在golang语言如何判断协程是否正常运行”?正确思路是你必须自己埋“探针”。 2 常见方式常见手段有 4 类(从简单到专业)最基础:done channel(是否结束) func DOtask(ctx context.Context, client *Client, done chan<- error) { defer func() { if r :...
1 简介
在golang语言如何判断协程是否正常运行”?正确思路是你必须自己埋“探针”。

2 常见方式
常见手段有 4 类(从简单到专业)
-
最基础:done channel(是否结束)
func DOtask(ctx context.Context, client *Client, done chan<- error) { defer func() { if r := recover(); r != nil { done <- fmt.Errorf("panic: %v", r) } }() // do work time.Sleep(2 * time.Second) done <- nil }
调用方:
done := make(chan error, 1)
go DOtask(ctx, &client, done)
select {
case err := <-done:
if err != nil {
log.Println("goroutine failed:", err)
} else {
log.Println("goroutine finished normally")
}
case <-time.After(5 * time.Second):
log.Println("goroutine still running")
}
能判断什么
能力 是否
是否完成 ✅
是否 panic ✅
是否卡死 ❌
- 2️⃣ 推荐:context + 心跳(heartbeat)
示例:心跳机制(非常重要)
func DOtask(ctx context.Context, hb chan<- struct{}) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// 心跳
select {
case hb <- struct{}{}:
default:
}
}
}
}
监控端:
hb := make(chan struct{}, 1)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go DOtask(ctx, hb)
for {
select {
case <-hb:
log.Println("goroutine alive")
case <-time.After(3 * time.Second):
log.Println("goroutine probably dead or blocked")
}
}
这是生产级做法
Kubernetes controller
ETL worker
后台任务系统
-
3️⃣ sync.WaitGroup(生命周期管理)
var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() DOtask(ctx, &client) }()
等待 goroutine 结束
wg.Wait()
适合:
程序退出前等待
批量 goroutine 管理
-
4️⃣ panic / error 监控(必须做)
强烈建议:每个 goroutine 都要 recovergo func() { defer func() { if r := recover(); r != nil { log.Printf("goroutine panic: %+v", r) } }() DOtask(ctx, &client) }()
否则:
goroutine panic 会 直接导致整个进程崩溃
3 没有“协程号(goroutine id)”
官方结论
Go 没有 goroutine id,也不鼓励你使用它
但现实中你可以“间接实现可追踪性”
**方案 1:**自己定义 TaskID(强烈推荐)
type Task struct {
ID string
}
func DOtask(ctx context.Context, task Task) {
log.Println("task start:", task.ID)
}
启动:
task := Task{ID: uuid.NewString()}
go DOtask(ctx, task)
比 goroutine id 更有业务意义
**方案 2:**在 context 中注入 ID
type ctxKey string
const taskIDKey ctxKey = "taskID"
ctx = context.WithValue(ctx, taskIDKey, "task-123")
go DOtask(ctx, &client)
在 goroutine 内:
id := ctx.Value(taskIDKey)
不推荐:hack goroutine id
runtime.Stack hack
func GoID() int64 {
var buf [64]byte
n := runtime.Stack(buf[:], false)
// "goroutine 10 [running]:"
fields := strings.Fields(string(buf[:n]))
id, _ := strconv.ParseInt(fields[1], 10, 64)
return id
}
问题:
非官方
不稳定
会被 Go 官方随时破坏
生产环境不建议
4 “检查 goroutine 状态”该怎么做
正确做法:业务级状态机
type TaskState int
const (
Running TaskState = iota
Stopped
Failed
)
type Task struct {
State atomic.Int32
}
在 goroutine 中更新:
task.State.Store(int32(Running))
监控线程读取:
state := task.State.Load()
5 实践一个“可观测 goroutine 模板”
func SafeGo(
ctx context.Context,
name string,
fn func(context.Context),
) <-chan error {
done := make(chan error, 1)
go func() {
defer func() {
if r := recover(); r != nil {
done <- fmt.Errorf("[%s] panic: %v", name, r)
}
}()
fn(ctx)
done <- nil
}()
return done
}
使用:
done := SafeGo(ctx, "sync-task", func(ctx context.Context) {
DOtask(ctx, &client)
})
select {
case err := <-done:
log.Println(err)
case <-time.After(10 * time.Second):
log.Println("task still running")
}
6 小结
Go 中没有 goroutine id 和状态查询机制;要判断协程是否“正常运行”,必须通过 channel / context / 心跳 / 业务状态来实现可观测性。
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)