dsdfd2322
dsdfd2322
2019-05-28 19:42
浏览 59
已采纳

这段具有非缓冲通道的代码是否会在Go中导致goroutine泄漏?

I'm writing some golang concurrency codes with goroutines and channels, suspecting that my code may cause goroutine leaks. My situation is similar to the following code, or you can open this go playground link.

func main() {
    numCount := 3
    numChan := make(chan int)

    for i := 0; i < numCount; i++ {
        go func(num int) {
            fmt.Printf("Adding num: %d to chan
", num)
            numChan <- num
            fmt.Printf("Adding num: %d to chan Done
", num)
        }(i)
    }

    time.Sleep(time.Second)
    panic("Goroutine Resource Leak Test")
} 

In my opinion, when the main goroutine returns, three goroutines are blocked sending to the unbuffered channel and there will be goroutine leak. This post goroutine leak with buffered channel in Go also suggests that So only if the channel was unbuffered the leak would occur.

The Go Programming Language suggests that:

There’s a handy trick we can use during testing: if instead of returning from main in the event of cancellation, we execute a call to panic, then the runtime will dump the stack of every goroutine in the program. If the main goroutine is the only one left, then it has cleaned up after itself. But if other goroutines remain, they may not have been properly canceled, or perhaps they have been canceled but the cancellation takes time; a little investigation may be worthwhile. The panic dump often contains sufficient information to distinguish these cases.

Therefore, I added panic("Goroutine Resource Leak Test") to the end of the main function to verify my assumption. However, the panic dump only contains main goroutine, that is, there is no resource leak.

Adding num: 0 to chan
Adding num: 1 to chan
Adding num: 2 to chan
panic: Goroutine Resource Leak Test

goroutine 1 [running]:
main.main()
    /tmp/sandbox011109649/prog.go:21 +0xc0

Can someone help explain

  • why there is no goroutine leak, or
  • how should I get the correct panic dump if there is leak

Any suggestion will be appreciated, thanks in advance!

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • dongping1922
    dongping1922 2019-05-28 20:01
    已采纳

    The problem with your code is twofold.

    First, there is, theoretically, a goroutine leak since any attempt to send a value to a channel with zero capacity (an unbuffered channel or a filled up buffered channel) blocks the sending goroutine until a receive operation is done on that channel.

    So, yes, by definition of how channels work, all your three goroutines will be blocked in the numChan <- num statement.

    Second, since some revision of Go, an unhandled panic by default dumps only the stack trace of the panicking goroutine. If you wish to dump the stacks of all the active goroutines, you'd have to tweak the runtime — from the documentation of the package runtime:

    The GOTRACEBACK variable controls the amount of output generated when a Go program fails due to an unrecovered panic or an unexpected runtime condition. By default, a failure prints a stack trace for the current goroutine, eliding functions internal to the run-time system, and then exits with exit code 2. The failure prints stack traces for all goroutines if there is no current goroutine or the failure is internal to the run-time. GOTRACEBACK=none omits the goroutine stack traces entirely. GOTRACEBACK=single (the default) behaves as described above. GOTRACEBACK=all adds stack traces for all user-created goroutines. GOTRACEBACK=system is like “all” but adds stack frames for run-time functions and shows goroutines created internally by the run-time. GOTRACEBACK=crash is like “system” but crashes in an operating system-specific manner instead of exiting. For example, on Unix systems, the crash raises SIGABRT to trigger a core dump. For historical reasons, the GOTRACEBACK settings 0, 1, and 2 are synonyms for none, all, and system, respectively. The runtime/debug package's SetTraceback function allows increasing the amount of output at run time, but it cannot reduce the amount below that specified by the environment variable. See https://golang.org/pkg/runtime/debug/#SetTraceback.


    Also note that you must not ever use timers for (simulating) synchronization: in a toy example this might work but in real life nothing prevents your three goroutines from not having a chance to be run during the time span your main goroutine spent in the call to time.Sleep — so the outcome might be that any number of spawned goroutines had run: from 0 to 3.

    Add there the fact that when main exits the runtime merely kills all the outstanding active goroutines, and the result of the test might be surprising at best.

    Hence a proper solution would be to

    • Merely print the stacks where needed,
    • Make sure you synchronize the sends by the matching receives:
    package main
    
    import (
        "fmt"
        "log"
        "runtime"
    )
    
    func dumpStacks() {
        buf := make([]byte, 32 * 1024)
        n := runtime.Stack(buf, true)
        log.Println(string(buf[:n]))
    }
    
    func main() {
        numCount := 3
        numChan := make(chan int, numCount)
    
        for i := 0; i < numCount; i++ {
            go func(num int) {
                fmt.Printf("Adding num: %d to chan
    ", num)
                numChan <- num
                fmt.Printf("Adding num: %d to chan Done
    ", num)
            }(i)
        }
    
        dumpStacks()
    
        for i := 0; i < numCount; i++ {
            <-numChan
        }
    }
    

    Playground.

    点赞 评论

相关推荐