为了账号安全,请及时绑定邮箱和手机立即绑定

Go例程泄漏在哪里?

Go例程泄漏在哪里?

Go
摇曳的蔷薇 2021-09-27 10:52:45
我正在尝试同时运行多个任务,并在出现任何错误时立即返回,而无需等待所有例程返回。代码如下所示。我已经去除了噪音以使其更容易消化,但如果泄漏不明显,我可以发布完整的代码。值得注意的是,我正在谷歌应用引擎上部署它。我无法在我的机器上重现泄漏,但是当我在// Consume the results评论后替换并发时,应用程序运行良好,但我不明白为什么,因为代码对我来说是正确的。package mainimport "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)}
查看完整描述

1 回答

?
青春有我

TA贡献1784条经验 获得超8个赞

一个循环只重复非阻塞检查直到它成功会导致难以诊断的问题(至少,它会过度使用 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 main


import "fmt"


func main() {

    c := make(chan struct{})

    go func() { c <- struct{}{} }()

    for {

        select {

        case <-c:

            fmt.Println("success")

            return

        default:

        }

    }

我不知道是否还有其他问题,但与样本相似的模式的挂起值得注意。


查看完整回答
反对 回复 2021-09-27
  • 1 回答
  • 0 关注
  • 185 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号