如何捕获通道死锁的异常?

如何捕获通道死锁的异常?

问题描述:

I am learning Go and am working on this lesson from the GoTours. Here's what I have so far.

package main

import (
    "fmt"
    "code.google.com/p/go-tour/tree"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    if t != nil {
        Walk(t.Left, ch)
        ch <- t.Value
        Walk(t.Right, ch)
    }
}

func main() {
    var ch chan int = make(chan int)
    go Walk(tree.New(1), ch)
    for c := range ch {
        fmt.Printf("%d ", c)    
    }
}

As you see, I try to test out my Walk function by printing out the values I wrote into a channel. However, I get the following error.

1 2 3 4 5 6 7 8 9 10 throw: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    main.go:25 +0x85

goroutine 2 [syscall]:
created by runtime.main
    /usr/local/go/src/pkg/runtime/proc.c:221

exit status 2

This error should be expected I think because I never close the channel. However, is there a way I could "catch" this deadlock error and deal with it programmatically?

Deadlock is similar to a nil pointer deference in that is represents a BUG in your program. This class of error is usually not recoverable for this reason.

As lbonn mentioned, the problem here is you need to "close(myChan)" your channel. If you do not do this the for-range loop, that the loop will wait for the next element forever.

You can try something like this:

func main() {
    var ch chan int = make(chan int)
    go func() {
        Walk(tree.New(1), ch)
        close(ch)
    }()
    for c := range ch {
        fmt.Printf("%d ", c)
    }
}

If you want to traverse the tree in parallel you will need to make further changes:

package main

import (
    "code.google.com/p/go-tour/tree"
    "fmt"
    "sync"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int, done *sync.WaitGroup) {
    if t != nil {
        done.Add(2)
        go Walk(t.Left, ch, done) //look at each branch in parallel
        go Walk(t.Right, ch, done)
        ch <- t.Value
    }
    done.Done()
}

func main() {
    var ch chan int = make(chan int, 64) //note the buffer size
    go func() {
        done := new(sync.WaitGroup)
        done.Add(1)
        Walk(tree.New(1), ch, done)
        done.Wait()
        close(ch)
    }()
    for c := range ch {
        fmt.Printf("%d ", c)
    }
}

No, you cannot recover from a deadlock.

This deadlocks because, the range construct iterates until the channel is closed. http://golang.org/ref/spec#For_statements

Here, you need to either close the channel when the tree is fully explored or use another construct.

For this example, you know that the trees are of size 10, so you can simply do a for loop from 1 to 10 and read from the channel once at each iteration.