Go常规泄漏在哪里?

时间:2015-03-26 01:14:04

标签: google-app-engine concurrency 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)
}

编辑:添加了一些工作代码。

编辑:添加的代码更接近实际代码,这可能解释了for循环的需要。

package main

import "fmt"
import "sync"
import "errors"

func main() {
    indexes := []int{1, 2, 3, 4, 5, 6, 7}
    indexesString := []string{"a", "b", "c", "d"}
    devChS := make(chan string, 1000)

    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")

                }
                wg.Add(1)
                go func(sub int) {
                    defer wg.Done()
                    for _, s := range indexesString {
                        devChS <- fmt.Sprintf("%s %s", s, sub)

                    }

                    return
                }(sub)
            }(sub)
            select {
            // If there is any error we better stop the
            // loop
            case <-stopCh:
                return
            default:
            }
            devCh <- sub
        }
        wg.Wait()
        close(devCh)
        close(devChS)
    }()
    // Consume the results
    var results = struct {
        integers []int
        strings  []string
    }{}
    var wt sync.WaitGroup
    wt.Add(1)
    go func() {
        defer wt.Done()
        for s := range devCh {
            results.integers = append(results.integers, s)
        }
        return
    }()
    wt.Add(1)
    go func() {
        defer wt.Done()
        for s := range devChS {
            results.strings = append(results.strings, 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, can return the results: %v", results)
}

1 个答案:

答案 0 :(得分:1)

tl; dr :一个循环除了重复非阻塞检查之外什么也不做,直到成功为止会导致难以诊断的麻烦(至少可以过度使用CPU);使用阻止检查可以修复它。

我对你案件的细节并不十分肯定;我写了一个像你这样的循环,在Playground上一直挂着“过程花了太长时间”,但是当我在本地运行它时它就完成了。

正如我评论的那样,我的目标也是设计更简单。


Go only has limited pre-emption of running goroutines:当阻塞操作(如I / O或通道操作或等待锁定)发生时,正在运行的线程仅对goroutine调度程序产生控制权。

所以使用GOMAXPROCS=1,如果(一个)运行的线程开始循环,那么其他任何东西都不一定有机会运行。

因此for { select { ...default: } }可以启动循环检查通道中的项目,但永远不会放弃对主线程的控制,以便另一个goroutine可以编写项目。当GOMAXPROCS超过1时,其他代码仍会运行,但是当它在App Engine(或Playground)上时为1。这种行为不仅取决于GOMAXPROCS,而且取决于首先运行的goroutine,这不一定是定义的。

要避免这种情况,请删除default:,以便select是一个阻止操作,当它无法接收项目时会产生调度程序,允许其他代码运行。您可以将此概括为可能循环执行非阻塞检查的其他情况;当阻塞呼叫不能时,它们中的任何一个都可以使资源忙于不断重新检查。当GOMAXPROCS>1或运行时的有限抢占为您节省时,轮询(如调用重复检查)仍然会消耗比阻塞更多的CPU。

例如,由于“进程花了太长时间”on the Playground,这会失败,尽管它在我的机器上可靠地完成:

package main

import "fmt"

func main() {
    c := make(chan struct{})
    go func() { c <- struct{}{} }()
    for {
        select {
        case <-c:
            fmt.Println("success")
            return
        default:
        }
    }
} 

我不知道是否还有其他问题,但是对于类似于样本的模式的悬念是值得注意的。