发生死锁是因为在开放通道上进行范围超出,但没有引发恐慌。

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

Deadlock Happens Due To Ranging Over Open Channel But Panic Not Thrown

问题

我有这样一段代码,我特意编写它来引发死锁和随后的恐慌抛出:

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Start:", time.Now())
	channel1 := make(chan string, 2)

	channel1 <- "Cat"
	channel1 <- "Dog"

	limiter := time.NewTicker(time.Millisecond * 500)
	for channel1Item := range channel1 {
		<-limiter.C
		fmt.Println(channel1Item)
	}

	fmt.Println("End:", time.Now())
}

在某种程度上,确实发生了死锁,代码会无限期地挂起,但它不会抛出恐慌。如果我移除限制器(ticker),它就会抛出恐慌。为什么限制器会阻止恐慌的抛出?

我目前正在学习Go语言,当我改变随机位以满足我的好奇心时,我偶然发现了这个例子。在我决定不关闭通道以查看会发生什么之后,发生了这种奇怪的行为(不会抛出恐慌)。

我知道在不被喂养的情况下遍历一个打开的通道通常会抛出恐慌,但似乎在循环内部有一个计时器会中断抛出恐慌的行为。

英文:

I have this code that I deliberately wrote to cause a deadlock and a subsequent panic throw:

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	fmt.Println(&quot;Start:&quot;, time.Now())
	channel1 := make(chan string, 2)

	channel1 &lt;- &quot;Cat&quot;
	channel1 &lt;- &quot;Dog&quot;

	limiter := time.NewTicker(time.Millisecond * 500)
	for channel1Item := range channel1 {
		&lt;-limiter.C
		fmt.Println(channel1Item)
	}

	fmt.Println(&quot;End:&quot;, time.Now())
}

In a way, a deadlock does happen and the code just hangs indefinitely, however, it does not throw a panic. If I remove the limiter (ticker), it then does. Why does the ticker prevent the panic from being thrown?

I am currently learning Go and I accidentally stumbled into this example when changing random bits to satiate my curiosity. The strange behavior (of a panic not being throw) happened after I decided not to close the channel to see what would happen.

I know ranging over an open channel that is not being fed generally throws a panic, but it seems that having a ticker inside the loop interrupts the panic throwing behavior.

答案1

得分: 3

因为在Go语言中的判断是,当一个goroutine试图在一个关闭的通道上发送一个有价值的消息时,它会触发panic。

在你的代码中,你没有关闭channel1,所以循环将无限期地等待channel1接收值。
<-limiter.C会定期向通道发送值
所以channel1不会排除panic,带有限制器的循环将会挂起
即使它没有任何新值

希望对你有帮助

英文:

Because the judgment in GO is that when a goroutine attempts to send a worthy message on a closed channel, it will trigger panic.

In your code, you did not close channel1, so the loop will wait indefinitely for channel1 to accept the value.
<- limiter. C will periodically send values to the channel
So channel1 won't rule out panic, loops with limiters will hang
Even if he doesn't have any new value

I hope it can be helpful to you

答案2

得分: 1

我知道在一个未被通常提供数据的开放通道上进行范围遍历会引发恐慌。如果只有"在开放通道上进行范围遍历"这一操作正在进行(或者所有其他goroutine也被阻塞),那么将检测到死锁并触发恐慌。在这个playground中,会检测到死锁:

func main() {
	c := make(chan int)
	for _ = range c {
	}
}

但在这个playground中不会检测到死锁:

func main() {
	go func() {
		for {
			time.Sleep(time.Nanosecond)
		}
	}()

	c := make(chan int)
	for _ = range c {
	}
}

这是因为,虽然for _ = range c {被阻塞,但goroutine并没有被阻塞(所以没有死锁)。

用于识别死锁的代码相对简单,如果没有可以运行的内容,就会触发恐慌。它不检查正在运行的代码是否有用!

根据Ticker的文档:

Ticker会调整时间间隔或丢弃滞后的接收者。

无论通道上是否有监听者,Ticker都会继续运行;这类似于在代码中添加以下内容(playground):

channel2 := make(chan time.Time, 1)
go func() {
	for {
		time.Sleep(time.Millisecond * 500)
		select {
		case channel2 <- time.Now():
		default:
		}
	}
}()

这个循环将继续运行,无论channel2上是否有监听者。因此,不会检测到死锁(也不会触发恐慌)。添加Ticker具有相同的影响;Ticker正在运行意味着不会检测到死锁。

英文:

>I know ranging over an open channel that is not being fed generally throws a panic

I guess that this is correct, a deadlock will be detected (and a panic triggered) if "ranging over an open channel" is the only thing that is happening (or all other goroutines are also blocked). A deadlock will be detected in this (playground):

func main() {
	c := make(chan int)
	for _ = range c {
	}
}

but not in this (playground):

func main() {
	go func() {
		for {
			time.Sleep(time.Nanosecond)
		}
	}()

	c := make(chan int)
	for _ = range c {
	}
}

That's because, while for _ = range c { is blocked, the goroutine is not (so there is no deadlock).

The code that identifies deadlocks is relatively simple and triggers a panic if there is nothing that can run. It does not check that the code that is running is doing something useful!

As per the docs for Ticker:

>The ticker will adjust the time interval or drop ticks to make up for slow receivers.

The ticker will continue to run whether or not something is listening on the channel; its similar to adding something like the following to your code (playground):

channel2 := make(chan time.Time, 1)
go func() {
	for {
		time.Sleep(time.Millisecond * 500)
		select {
		case channel2 &lt;- time.Now():
		default:
		}
	}
}()

This loop will continue to run regardless of whether something is listening on channel2 or not. As such, no deadlock will be detected (and no panic triggered). Adding the Ticker has the same impact; the fact that the Ticker is running means that no deadlock will be detected.

huangapple
  • 本文由 发表于 2023年7月13日 16:31:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76677389.html
匿名

发表评论

匿名网友

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

确定