Go例程泄漏在哪里?

我正在尝试同时运行多个任务,并在出现任何错误时立即返回,而无需等待所有例程返回。代码如下所示。我已经去除了噪音以使其更容易消化,但如果泄漏不明显,我可以发布完整的代码。值得注意的是,我正在谷歌应用引擎上部署它。我无法在我的机器上重现泄漏,但是当我在// Consume the results评论后替换并发时,应用程序运行良好,但我不明白为什么,因为代码对我来说是正确的。


package main


import "fmt"

import "sync"

import "errors"


func main() {

    indexes := []int{1, 2, 3, 4, 5, 6, 7}

    devCh := make(chan int, 7)

    stopCh := make(chan struct{})

    errCh := make(chan error, 7)

    var wg sync.WaitGroup

    go func() {

        for _, sub := range indexes {

            wg.Add(1)

            go func(sub int) {

                defer wg.Done()

                // some code which creates other

                // wait groups and spans other go routines

                // handle errors

                if sub == 99 { // unreachable 

                    errCh <- errors.New("new error")


                }

            }(sub)

            select {

            // If there is any error we better stop the

            // loop

            case <-stopCh:

                return

            default:

            }

            devCh <- sub

        }

        wg.Wait()

        close(devCh)

    }()

    // Consume the results

    var results []int

    var wt sync.WaitGroup

    wt.Add(1)

    go func() {

        defer wt.Done()

        for s := range devCh {

            results = append(results, s)

        }

        return

    }()

    done := make(chan struct{})

    go func() {

        wt.Wait()

        close(done)

    }()


L:

    for {

        select {

        case err := <-errCh:

            fmt.Printf("error was %v", err)

            close(stopCh)

            return

        case <-done:

            break L

        default:

        }

    }

    fmt.Printf("all done, %v", results)

}


摇曳的蔷薇
浏览 186回答 1
1回答

青春有我

一个循环只重复非阻塞检查直到它成功会导致难以诊断的问题(至少,它会过度使用 CPU);使用阻塞检查可以修复它。我不太确定你的案子的细节。我写了一个像你这样的循环,它在 Playground 上始终挂起“进程花费太长时间”,但是当我在本地运行它时,它确实完成了。正如我所评论的,我的目标是更简单的设计。Go 仅对运行中的 goroutine 具有有限的抢占权:运行中的线程仅在发生阻塞操作(如 I/O 或通道操作或等待获取锁)时才将控制权交给 goroutine 调度程序。因此GOMAXPROCS=1,如果(一个)正在运行的线程开始循环,则其他任何东西都不一定有机会运行。for { select { ...default: } }因此,A可以开始循环检查通道中的项目,但永远不会放弃对主线程的控制,以便另一个 goroutine 可以写入项目。当GOMAXPROCS超过 1 时,其他代码无论如何都会运行,但不会像 App Engine(或 Playground)上的 1 那样运行。行为不仅取决于GOMAXPROCS,还取决于哪个 goroutine 首先运行,这不一定定义。为了避免这种情况,删除default:soselect是一个阻塞操作,当调度程序无法接收项目时,它会产生阻塞操作,允许其他代码运行。您可以将其推广到其他可能循环进行非阻塞检查的情况;当阻塞调用不会时,它们中的任何一个都可以使资源忙于不断地重新检查。当GOMAXPROCS>1或运行时的有限抢占拯救您时,轮询(如调用重复检查)仍然可以比阻塞消耗更多的 CPU。例如,这在 Playground 上因“过程花费太长时间”而失败,尽管烦人的是它在我的机器上可靠地完成:package mainimport "fmt"func main() {&nbsp; &nbsp; c := make(chan struct{})&nbsp; &nbsp; go func() { c <- struct{}{} }()&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-c:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("success")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}&nbsp;我不知道是否还有其他问题,但与样本相似的模式的挂起值得注意。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go