进行例行泄漏修复

时间:2018-09-18 22:06:25

标签: go concurrency goroutine channels

我目前正在从事一项小型服务。从我的测试中,我编写的代码在与上下文有关的某些情况下有可能泄漏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
}

假设地,如果客户端取消了上下文,或者超时发生在流程函子有机会在未缓冲的通道之一(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
    }
}

此方法可确保每次尝试发送频道时,首先检查上下文是否已完成或取消。如果是这样,则它不会尝试发送并返回。我很确定这可以解决在第一个流程函子中发生的所有go例程泄漏。

有更好的方法吗?也许我全都错了。

0 个答案:

没有答案