英文:
How test functions are called in Go?
问题
我正在学习Go语言,现在有一个问题。
我刚刚在测试缓冲和非缓冲通道。
在编写测试代码时,出现了一个我无法理解结果的情况。
func BenchmarkUnbufferedChannelInt(b *testing.B) {
ch := make(chan int)
go func() {
for i := 0; i < b.N; i++ {
ch <- i
}
}()
for {
<-ch
}
}
上面的代码不会停止,因为<-ch
会使goroutine永远阻塞。然而,下面的代码不会创建阻塞状态,并且测试成功。
func BenchmarkUnbufferedChannelInt(b *testing.B) {
ch := make(chan int)
go func() {
for {
<-ch
}
}()
for i := 0; i < b.N; i++ {
ch <- i
}
}
BenchmarkUnbufferedChannelInt
BenchmarkUnbufferedChannelInt-12 8158381 142.1 ns/op
PASS
据我所知,goroutine之间没有父子关系,因此除了主goroutine之外,一个goroutine的终止不会影响其他goroutine的终止。
从理论上讲,上述两个代码都应该最终被永久阻塞。
但我不知道为什么只有第二个代码成功了。
我想知道Go如何调用测试函数。假设测试函数所在的goroutine具有与主goroutine相同的特性,我可以理解上述代码的结果。
但如果不是这样,我就不明白了。在两个测试中,一个goroutine最终都应该被永久阻塞。
你能解释一下上述结果吗?谢谢!
英文:
I'm studying Go language and I have a question now.
I was just testing buffered and unbuffered channels.
While writing the test code for it, a situation occurred that I couldn't understand the result.
func BenchmarkUnbufferedChannelInt(b *testing.B) {
ch := make(chan int)
go func() {
for i := 0; i < b.N; i++ {
ch <- i
}
}()
for {
<-ch
}
}
The above code doesn't stop, because <-ch
makes the goroutine blocked forever. However, the code below does not create an blocked status and the testing succeeds.
func BenchmarkUnbufferedChannelInt(b *testing.B) {
ch := make(chan int)
go func() {
for {
<-ch
}
}()
for i := 0; i < b.N; i++ {
ch <- i
}
}
BenchmarkUnbufferedChannelInt
BenchmarkUnbufferedChannelInt-12 8158381 142.1 ns/op
PASS
As far as I know, there is no parent-child relationship between goroutines, so the termination of one goroutine does not affect the termination of the other goroutines except for main goroutine.
Theoretically, both of the above codes should eventually be blocked forever.
But I don't know why only the second code succeeds.
I wonder how Go calls test functions. Assuming that the goroutine where the test function runs has the same characteristics as the main goroutine, I can understand the result of the above codes.
But if not, I don't understand. In both tests, a goroutine should eventually be blocked forever.
Can you explain the above result? please!
答案1
得分: 4
上面的代码没有停止,因为<-ch
使得goroutine永远被阻塞。
不,它被阻塞是因为无限的for
循环。
然而,下面的代码不会创建一个阻塞状态,测试也会成功。
是的,因为for
循环会运行你想要的基准测试的循环次数。一旦这个循环结束,你启动的goroutine就会被遗忘,从而导致泄漏。请参考https://www.ardanlabs.com/blog/2018/11/goroutine-leaks-the-forgotten-sender.html:如果你启动了一个你期望最终终止但实际上永远不会终止的Goroutine,那么它就会泄漏。
英文:
> The above code doesn't stop, because <-ch
makes the goroutine blocked forever.
No, it's being blocked because of the infinite for
loop.
> However, the code below does not create an blocked status and the testing succeeds.
Right, because the for
loop runs for as many loops as you want your benchmark to run. Once this loop ends, the goroutine you spun up is forgotten and you have a leak. See https://www.ardanlabs.com/blog/2018/11/goroutine-leaks-the-forgotten-sender.html: If you start a Goroutine that you expect to eventually terminate but it never does then it has leaked.
答案2
得分: 2
在你的第二个示例中,goroutine 会无限期地阻塞,这在技术上是一个泄漏,但不会阻塞程序本身。
在你的第一个示例中,你在一个 goroutine 外部无限循环,所以程序会被阻塞。这不是 Go 语言特有的,也与从通道接收无关。你只是在没有退出条件的情况下进行循环,比如在其他任何语言中的 while (true) {}
。
确保你的 goroutine 不会无限循环以防止泄漏。此外,通过通道进行范围遍历会在通道被清空和关闭后停止,所以你可以在 goroutine 中在填充完通道后关闭它,并对其进行范围遍历:
func BenchmarkUnbufferedChannelInt(b *testing.B) {
ch := make(chan int)
go func() {
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
}()
for _ := range ch {
}
}
英文:
In your second example, the goroutine blocks indefinitely, which is technically a leak but won't block the program itself.
In your first example, you're looping indefinitely outside a goroutine, so the program will block. This isn't specific to Go and isn't really related to receiving from the channel. You're just looping with no exit condition e.g. while (true) {}
in about any other language.
Make sure you aren't looping indefinitely in the goroutine to prevent it from leaking. Also, ranging over a channel stops once the channel is emptied and closed, so you can close it in the goroutine once done filling it and range over it instead:
func BenchmarkUnbufferedChannelInt(b *testing.B) {
ch := make(chan int)
go func() {
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
}()
for _ := range ch {
}
}
答案3
得分: 1
我弄清楚了为什么会发生这种情况。
第一个例子:发生死锁是因为即使没有更多的数据可供消费,<-ch
语句仍然被使用,因为生产者 goroutine 的迭代次数比消费者 goroutine 的迭代次数多。即使消费者 goroutine 被终止,运行测试函数的程序本身并不会终止,所以最终会发生死锁。
第二个例子:消费者 goroutine 的迭代次数少于生产者 goroutine 的迭代次数。当消费者 goroutine 的循环结束时,相应的测试函数返回并且程序结束。此时,生产者 goroutine 的循环尚未结束,但程序本身已经结束,所以会发生 goroutine 泄漏。
我花了很长时间才理解这个情况,感谢大家的回复。
英文:
I figured out why this happened.
First example: Deadlock occurs because <-ch
statement is used even though there is no more data to consume because the producer goroutine iterates more times than the producer goroutine iterates. Even if the consumer goroutine is terminated, the program itself in which the test function is running does not terminate, so deadlock eventually occurs.
Second example: The number of iterations of the consumer goroutine is less than the number of iterations of the producer goroutine. When the loop of the consumer goroutine is finished, the corresponding test function returns and the program ends. At this time, the loop of the producer goroutine has not finished yet, but the program itself has ended, so a goroutine leak occurs.
It took me a long time to understand the situation, thank you all for your replies.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论