2

我想尝试 FizzBu​​zz 测试(为什么程序员不能编程),并使用了 Go。它基本上是从 1 到 100 循环,当循环计数器可被 3 整除时打印“Fizz”,当可被 5 整除时打印“Buzz”,当可被两者整除时打印“FizzBu​​zz”,否则只打印数字。

在迭代和递归完成之后,我想同时(或通过使用通道)完成它。我想出了以下代码,令我惊讶的是:

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

我不明白 for 循环如何以及为什么停止。没有中断条件或返回语句。为什么它最终会结束运行?

4

3 回答 3

1

它真的不好用。

一段时间后,由于剩余的 go-routine 等待没有 goroutine 推送的通道,会发生损耗。因此,您所拥有的是死锁(这是结束程序的致命错误),而不是干净的结局。

总之,它“有效”是因为 go 引擎足够聪明,可以检测到死锁。

于 2014-09-03T16:35:29.040 回答
0

@dystroy 很好地回答了您的问题,但是您可以通过以下方式修复代码。

整洁退出的一种方法是使用退出通道,我们通过关闭它来发出信号。(通过关闭通道发出信号很有用,因为可以同时收听多个 goroutine)。

不过,还有其他方法可以做到这一点——如果你只有一个输出通道,那么你range在它上面读取结果并close在你完成时读取它。您可以轻松地重新编写它以这种方式工作。

您可以使用 async.Waitgroup来确保 go 例程也已完成。

操场

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")
}
于 2014-09-03T22:16:34.143 回答
0

@OneOfOne 提到了 sync.WaitGroup 方法,我认为该方法最符合您在 Go 中执行此操作的方式。考虑到 goroutine 非常便宜并且可以并行解决问题,我们可以为每个输入创建一个 goroutine,并将结果发送到缓冲通道。

//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)
}

游乐场代码:http ://play.golang.org/p/80UafMax7M

于 2014-09-04T09:42:42.370 回答