You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何统计因异常或错误终止的Go协程数量?

解决方案:统计Go协程异常终止数量

搞定这个需求的核心思路其实很简单:给每个协程套一层「安全包装」,既处理业务函数返回的错误,又能捕获未预期的panic,再用线程安全的计数器统计异常情况。下面一步步给你拆解实现细节:

1. 先搞个线程安全的计数器

因为多个协程会同时更新计数,必须避免竞态条件。用sync/atomic的原子操作是最高效的选择:

var failedCount int64

2. 写个协程包装函数:统一处理错误和panic

把你的业务逻辑(调用receiveValues)放进这个包装函数里,它会帮你做两件事:

  • 检查receiveValues的返回错误,有错误就计数
  • 用recover()捕获panic,把panic也当作异常终止来计数

示例包装函数:

func wrapReceive(ctx context.Context, valueChan <-chan YourValueType) {
    // 先放panic恢复的defer,确保能捕获所有panic
    defer func() {
        if r := recover(); r != nil {
            // 这里可以加日志,记录panic的具体信息方便排查
            log.Printf("协程意外panic退出: %v", r)
            atomic.AddInt64(&failedCount, 1)
        }
    }()

    // 执行你的业务逻辑
    err := receiveValues(ctx, valueChan)
    if err != nil {
        // 这里可以根据需求过滤错误,比如上下文取消的错误可能不需要统计
        if err != context.Canceled && err != context.DeadlineExceeded {
            log.Printf("receiveValues执行失败: %v", err)
            atomic.AddInt64(&failedCount, 1)
        }
    }
    // 正常执行完成的话,啥也不用做,不计数
}

3. 启动协程时用包装函数替代原逻辑

原来直接启动协程的地方,换成调用这个包装函数就行:

// 假设你要启动10个协程
for i := 0; i < 10; i++ {
    go wrapReceive(ctx, yourValueChan)
}

4. 等待所有协程结束后读取统计结果

记得用sync.WaitGroup等所有协程跑完,再安全读取计数器的值:

// 假设你用wg来等待协程
wg.Wait()
fmt.Printf("异常终止的协程总数: %d\n", atomic.LoadInt64(&failedCount))

额外优化小建议

  • 如果需要区分「业务错误」和「panic」的数量,可以搞两个原子计数器分别统计
  • 在recover()里可以加更多上下文信息,比如协程编号、正在处理的value值,方便后续排查问题
  • 要是receiveValues内部还有嵌套函数可能panic,一定要保证defer recover()放在包装函数的最开头,确保所有panic都能被捕获

最后给你贴个完整的示例代码,包含WaitGroup和模拟业务逻辑:

package main

import (
    "context"
    "fmt"
    "log"
    "sync"
    "sync/atomic"
    "time"
)

// 模拟你的值类型
type Data struct {
    ID int
}

var failedCount int64

// 模拟你的receiveValues函数,随机返回错误或panic
func receiveValues(ctx context.Context, dataChan <-chan Data) error {
    select {
    case d := <-dataChan:
        if d.ID%3 == 0 {
            return fmt.Errorf("处理数据ID%d失败", d.ID)
        }
        if d.ID%5 == 0 {
            panic(fmt.Sprintf("处理数据ID%d时触发panic", d.ID))
        }
        log.Printf("成功处理数据ID%d", d.ID)
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

func wrapReceive(ctx context.Context, dataChan <-chan Data, wg *sync.WaitGroup) {
    defer wg.Done()
    defer func() {
        if r := recover(); r != nil {
            log.Printf("协程panic退出: %v", r)
            atomic.AddInt64(&failedCount, 1)
        }
    }()

    err := receiveValues(ctx, dataChan)
    if err != nil {
        if err != context.Canceled && err != context.DeadlineExceeded {
            log.Printf("业务逻辑错误: %v", err)
            atomic.AddInt64(&failedCount, 1)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    dataChan := make(chan Data, 20)
    var wg sync.WaitGroup
    workerNum := 5

    // 启动工作协程
    for i := 0; i < workerNum; i++ {
        wg.Add(1)
        go wrapReceive(ctx, dataChan, &wg)
    }

    // 发送测试数据
    go func() {
        for i := 1; i <= 20; i++ {
            select {
            case dataChan <- Data{ID: i}:
            case <-ctx.Done():
                return
            }
        }
        close(dataChan)
    }()

    wg.Wait()
    log.Printf("最终异常终止的协程数量: %d", atomic.LoadInt64(&failedCount))
}

这个方案能完美覆盖你要的两种异常场景:业务函数返回错误、协程意外panic,而且计数绝对线程安全。

内容的提问来源于stack exchange,提问作者p-ray

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:14:06