首页 > 解决方案 > 进行常规泄漏修复

问题描述

我目前正在从事一项小型服务。根据我的测试,我编写的代码在与上下文相关的某些情况下可能会泄漏 go 例程。有没有一种好的和/或惯用的方法来解决这个问题?我在下面提供了一些示例代码。

func Handle(ctx context.Context, r *Req) (*Response, error) {
    ctx, cancel := context.WithTimeout(ctx, time.Second * 5)
    defer cancel()

    resChan := make(chan Response)
    errChan := make(chan error)

    go process(r, resChan, errChan)

    select {
    case ctx.Done():
        return nil, ctx.Err()
    case res := <-resChan:
        return &res, nil
    case err := <-errChan:
        return nil, err
    }
}

func process(r *Req, resChan chan<- Response, errChan chan<- error) {
    defer close(errChan)
    defer close(resChan)

    err := doSomeWork()
    if err != nil {
        errChan <- err
        return
    }

    err = doSomeMoreWork()
    if err != nil {
        errChan <- err
        return
    }

    res := Response{}
    resChan <- res
}

假设,如果客户端取消上下文或超时发生在进程 func 有机会在无缓冲通道之一(resChan,errChan)上发送之前,Handle 将没有通道读取器,并且通道上的发送将无限期阻塞没有读者。由于在这种情况下进程不会返回,因此通道也不会关闭。

我提出了 process2 作为解决方案,但我不禁认为我做错了什么,或者有更好的方法来处理这个问题。

func process2(ctx context.Context, r *Req, resChan chan<- Response, errChan chan<- error) {
    defer close(errChan)
    defer close(resChan)

    err := doSomeWork()
    select {
    case <-ctx.Done():
        return
    default:
        if err != nil {
            errChan <- err
            return
        }
    }

    err = doSomeMoreWork()
    select {
    case <-ctx.Done():
        return
    default:
        if err != nil {
            errChan <- err
            return
        }
    }

    res := Response{}
    select{
    case <-ctx.Done():
        return
    default:
        resChan <- res
    }
}

这种方法确保每次尝试发送通道时,首先检查上下文是否已完成或取消。如果是,则它不会尝试发送并返回。我很确定这修复了第一个进程函数中发生的任何 goroutine 泄漏。

有没有更好的办法?也许我错了。

标签: goconcurrencygoroutinechannels

解决方案


推荐阅读