为什么这个 Golang 函数**不会**永远运行?

huangapple go评论89阅读模式
英文:

Why does this golang function _not_ run forever?

问题

我想尝试FizzBuzz测试(为什么程序员不能编程),并使用Go语言。基本上是从1循环到100,当循环计数器可以被3整除时打印"Fizz",可以被5整除时打印"Buzz",同时可以被3和5整除时打印"FizzBuzz",否则只打印数字。

在使用迭代和递归的方法之后,我想使用并发(或使用通道)来完成。我想出了以下代码,令我惊讶的是它能正常工作:

func fizzbuzzconc() {
    // 用于通信的通道
    fizzchan := make(chan int)
    buzzchan := make(chan int)
    fizzbuzzchan := make(chan int)
    nonechan := make(chan int)

    // 启动Go协程来计算FizzBuzz挑战
    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
            }
        }
    }()

    // 这个for循环什么时候结束?
    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循环是如何结束的。它没有终止条件,也没有返回语句。为什么它最终会停止运行?

英文:

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 &lt;= 100; i++ {
            if i % 3 == 0 &amp;&amp; i % 5 == 0 {
                fizzbuzzchan &lt;- i
            } else if i % 3 == 0 {
                fizzchan &lt;- i
            } else if i % 5 == 0 {
                buzzchan &lt;- i
            } else {
                nonechan &lt;- i
            }
        }
    }()

    // When or how does this for loop end?
    for {
        select {
        case i := &lt;-fizzchan:
            fmt.Println(i, &quot;Fizz&quot;)
        case i := &lt;-buzzchan:
            fmt.Println(i, &quot;Buzz&quot;)
        case i := &lt;-fizzbuzzchan:
            fmt.Println(i, &quot;FizzBuzz&quot;)
        case i  := &lt;-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?

答案1

得分: 1

它并不真正有效。

经过一段时间后,由于剩余的 Go 协程在等待一个没有协程推送的通道,会发生逐渐减少的情况。所以你会遇到一个死锁(这是一个致命错误,会终止程序),而不是一个干净的结束。

总结起来,它“工作”是因为 Go 引擎足够智能,能够检测到死锁。

英文:

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.

答案2

得分: 0

@dystroy已经很好地回答了你的问题,但是这里是你可能修复代码的方法。

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

当然,还有其他的方法可以实现这个目的 - 如果你只有一个输出通道,那么你可以使用range来读取结果并在完成后close它。你可以很容易地重新编写代码以实现这种方式。

你可以使用sync.Waitgroup来确保goroutine已经完成。

func main() {
    // 用于通信的通道
    fizzchan := make(chan int)
    buzzchan := make(chan int)
    fizzbuzzchan := make(chan int)
    nonechan := make(chan int)
    quit := make(chan struct{})

    // 启动goroutine来计算fizzbuzz挑战
    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)
    }()

    // 这个for循环什么时候结束?
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")
}

Playground

英文:

@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 &lt;= 100; i++ {
			if i%3 == 0 &amp;&amp; i%5 == 0 {
				fizzbuzzchan &lt;- i
			} else if i%3 == 0 {
				fizzchan &lt;- i
			} else if i%5 == 0 {
				buzzchan &lt;- i
			} else {
				nonechan &lt;- i
			}
		}
		close(quit)
	}()

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

答案3

得分: 0

@OneOfOne提到了sync.WaitGroup方法,我认为这是在Go中完成此任务的最佳方法。考虑到goroutine非常廉价,并且问题可以并行解决,我们可以为每个输入创建一个goroutine,并将结果发送到一个带缓冲的通道。

//要检查的大小
size := 100

results := make(chan Result, size)

//创建WaitGroup并设置latch大小。
var wg sync.WaitGroup
wg.Add(size)

//为每个并行操作创建一个goroutine
for i := 1; i <= size; i++ {
i := i //将值绑定到闭包中
go func() {
results <- fizzbuzz(i)
wg.Done() //释放一个latch
}()
}

//等待所有goroutine完成。
wg.Wait()

//关闭通道,以便我们可以退出下面的循环。
close(results)

//遍历结果,并在通道关闭后退出。
for x := range results {
fmt.Printf("i: %d result: %s\n", x.Nr, x.Val)
}

Playground代码:http://play.golang.org/p/80UafMax7M

英文:

@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 &lt;= size; i++ {
	i := i  //bind value into closure
	go func() {
		results &lt;- 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(&quot;i: %d result: %s\n&quot;, x.Nr, x.Val)
}

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

huangapple
  • 本文由 发表于 2014年9月4日 00:30:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/25649384.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定