简体   繁体   中英

Why does this golang function _not_ run forever?

I wanted to try the FizzBuzz test ( Why can't programmers program ), and used Go. It's basically looping from 1 to 100, and printing "Fizz" when the loop counter is divisible by 3, "Buzz" when divisible by 5, "FizzBuzz" when divisible by both and else just print the number.

After doing it iteratively and recursively, I wanted to do it concurrently (or by using channels). I came up with the following code, which worked to my surprise:

func fizzbuzzconc() {
    // Channels for communication
    fizzchan := make(chan int)
    buzzchan := make(chan int)
    fizzbuzzchan := make(chan int)
    nonechan := make(chan int)

    // Start go routine to calculate fizzbuzz challenge
    go func() {
        for i := 1; i <= 100; i++ {
            if i % 3 == 0 && i % 5 == 0 {
                fizzbuzzchan <- i
            } else if i % 3 == 0 {
                fizzchan <- i
            } else if i % 5 == 0 {
                buzzchan <- i
            } else {
                nonechan <- i
            }
        }
    }()

    // When or how does this for loop end?
    for {
        select {
        case i := <-fizzchan:
            fmt.Println(i, "Fizz")
        case i := <-buzzchan:
            fmt.Println(i, "Buzz")
        case i := <-fizzbuzzchan:
            fmt.Println(i, "FizzBuzz")
        case i  := <-nonechan:
            fmt.Println(i, i)
        }
    }
}

I can't understand how and why the for loop stops. There is no break condition, or a return statement. Why does it eventually finish running?

It doesn't really work well.

What happens, after a time, is that there's an attrition due to the remaining go-routine waiting for a channel where no goroutine pushes. So what you have is a dead-lock (which is a fatal error ending the program), not a clean ending.

In summary it "works" because the go engine is smart enough to detect the dead lock.

@dystroy has answered your question very well, however here is how you might fix your code.

One way of exiting tidily is using a quit channel which we signal by closing it. (Signalling by closing a channel is useful because more than one go routine can be listening on it at once).

There are other ways of doing this though - if you only have one output channel then you range over it to read the result and close it when you finished. You could easily re-write this to work that way.

You can use a sync.Waitgroup to make sure the go routine has finished also.

Playground

func main() {
    // Channels for communication
    fizzchan := make(chan int)
    buzzchan := make(chan int)
    fizzbuzzchan := make(chan int)
    nonechan := make(chan int)
    quit := make(chan struct{})

    // Start go routine to calculate fizzbuzz challenge
    go func() {
        for i := 1; i <= 100; i++ {
            if i%3 == 0 && i%5 == 0 {
                fizzbuzzchan <- i
            } else if i%3 == 0 {
                fizzchan <- i
            } else if i%5 == 0 {
                buzzchan <- i
            } else {
                nonechan <- i
            }
        }
        close(quit)
    }()

    // When or how does this for loop end?
OUTER:
    for {
        select {
        case i := <-fizzchan:
            fmt.Println(i, "Fizz")
        case i := <-buzzchan:
            fmt.Println(i, "Buzz")
        case i := <-fizzbuzzchan:
            fmt.Println(i, "FizzBuzz")
        case i := <-nonechan:
            fmt.Println(i, i)
        case <-quit:
            break OUTER
        }
    }
    fmt.Println("All done")
}

@OneOfOne mentioned the sync.WaitGroup method which I think falls most in line with how you would do this in Go. Considering goroutines are very cheap and the problem can be solved in parallel, we can create a go routine for each input and send the results on a buffered channel.

//Size to check
size := 100

results := make(chan Result, size)

// Create the WaitGroup and set the latch size.
var wg sync.WaitGroup
wg.Add(size)

// Create a goroutine for each parallel operation
for i := 1; i <= size; i++ {
    i := i  //bind value into closure
    go func() {
        results <- fizzbuzz(i)
        wg.Done()  //release a latch
    }()
}

//wait for all the goroutines to finish.
wg.Wait()

//close the channel so we can exit the below for loop.
close(results)

//Range over the results and exit once the channel has closed.
for x := range results {
    fmt.Printf("i: %d result: %s\n", x.Nr, x.Val)
}

Playground code: http://play.golang.org/p/80UafMax7M

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM