为什么这个golang函数_not_永远运行?

时间:2014-09-03 16:30:51

标签: go channel fizzbuzz

我想尝试FizzBu​​zz测试(Why can't programmers program),并使用Go。它基本上从1到100循环,并打印" Fizz"当循环计数器可被3整除时," Buzz"当被5整除时," 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循环的停止方式和原因。没有休息条件或退货声明。为什么它最终会完成运行?

3 个答案:

答案 0 :(得分:1)

它并没有真正奏效。

经过一段时间后会发生的事情是,由于剩余的常规程序正在等待没有goroutine推动的通道,所以会有一些损耗。所以你所拥有的是一个死锁(这是一个致命的错误,结束了程序),而不是一个干净的结局。

总而言之,&#34;工作&#34;因为go引擎足够智能来检测死锁。

答案 1 :(得分:0)

@dystroy已经很好地回答了您的问题,但是以下是您修复代码的方法。

退出整齐的一种方法是使用退出通道,我们通过关闭它来发出信号。 (通过关闭频道发出信号非常有用,因为不止一个例行程序可以同时收听它。)

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

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

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

答案 2 :(得分:0)

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

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