Python-style generators in Go

后端 未结 4 2070
梦谈多话
梦谈多话 2021-02-01 16:06

I\'m currently working through the Tour of Go, and I thought that goroutines have been used similarly to Python generators, particularly with Question 66. I thought 66 looked co

4条回答
  •  既然无缘
    2021-02-01 16:41

    I like @tux21b's answer; having the channel created in the fib() function makes the calling code nice and clean. To elaborate a bit, you only need a separate 'quit' channel if there's no way to tell the function when to stop when you call it. If you only ever care about "numbers up to X", you can do this:

    package main
    
    import "fmt"
    
    func fib(n int) chan int {
        c := make(chan int)
    
        go func() {
            x, y := 0, 1
    
            for x < n {
                c <- x
                x, y = y, x+y
            }
    
            close(c)
        }()
    
        return c
    }
    
    func main() {
        // Print the Fibonacci numbers less than 500
        for i := range fib(500) {
            fmt.Println(i)
        }
    }
    

    If you want the ability to do either, this is a little sloppy, but I personally like it better than testing the condition in the caller and then signalling a quit through a separate channel:

    func fib(wanted func (int, int) bool) chan int {
        c := make(chan int)
    
        go func() {
            x, y := 0, 1
    
            for i := 0; wanted(i, x); i++{
                c <- x
                x, y = y, x+y
            }
    
            close(c)
        }()
    
        return c
    }
    
    func main() {
        // Print the first 10 Fibonacci numbers
        for n := range fib(func(i, x int) bool { return i < 10 }) {
            fmt.Println(n)
        }
    
        // Print the Fibonacci numbers less than 500
        for n := range fib(func(i, x int) bool { return x < 500 }) {
            fmt.Println(n)
        }
    }
    

    I think it just depends on the particulars of a given situation whether you:

    1. Tell the generator when to stop when you create it by
      1. Passing an explicit number of values to generate
      2. Passing a goal value
      3. Passing a function that determines whether to keep going
    2. Give the generator a 'quit' channel, test the values yourself, and tell it to quit when appropriate.

    To wrap up and actually answer your questions:

    1. Increasing the channel size would help performance due to fewer context switches. In this trivial example, neither performance nor memory consumption are going to be an issue, but in other situations, buffering the channel is often a very good idea. The memory used by make (chan int, 100) hardly seems significant in most cases, but it could easily make a big performance difference.

    2. You have an infinite loop in your fibonacci function, so the goroutine running it will run (block on c <- x, in this case) forever. The fact that (once c goes out of scope in the caller) you won't ever again read from the channel you share with it doesn't change that. And as @tux21b pointed out, the channel will never be garbage collected since it's still in use. This has nothing to do with closing the channel (the purpose of which is to let the receiving end of the channel know that no more values will be coming) and everything to do with not returning from your function.

提交回复
热议问题