In Go what happens if you write to closed channel? Can I treat channels as deterministic RE destruction?

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

In Go what happens if you write to closed channel? Can I treat channels as deterministic RE destruction?

问题

好的,我会为你翻译以下内容:

好吧,Stack Overflow(SO)正在警告我标题过于主观,请允许我解释一下。现在我正在研究Go语言,我已经阅读了规范,观看了一些IO的讲座,它看起来很有趣,但我有一些问题。

我最喜欢的一个例子是一个select语句,它监听来自"DoAfter()"函数返回的通道,该通道会在未来的某个时间发送一些东西。

类似于这样(这可能不起作用,只是伪代码):

to := Time.DoAfter(1000 * Time.MS)
select:
    case <-to:
        return nil //超时
    case d := <-waitingfor:
        return d

假设我们等待的事件非常快地发生,所以这个函数返回后不再监听to通道,那么在DoAfter函数中会发生什么?

我知道并且认同你不应该测试通道,例如:

if(chanToSendTimeOutOn.isOpen()) {
    chanToSendTimeOutOn<-true
}

我喜欢通道的同步特性,例如在上面的例子中,函数可能在isOpen()测试之后但发送true之前返回。我真的不喜欢这种测试,因为它违背了通道的用途——隐藏锁等。

我已经阅读了规范并了解了运行时的panic和恢复机制,但在这个例子中,我们在哪里进行恢复?等待发送超时的部分是一个goroutine还是某种"对象"?我想象中是一个"对象",它有一个按给定时间排序的待发送事物列表,并且会按照正确的顺序将TimeAfter请求添加到队列中并处理。我不确定它在哪里有机会进行实际的恢复。

如果它为每个goroutine生成了一个独立的定时器(当然由运行时管理,所以线程实际上不会因为时间而阻塞),那么又会有什么机会进行恢复呢?

我问题的另一部分是关于通道的生命周期,我想象它们是引用计数的,至少那些可读的通道是引用计数的,所以如果没有任何地方持有可读引用,它们就会被销毁。我称之为确定性。对于你可以形成的"点对点"拓扑结构,只要遵循Go的"通过通道发送数据,不要直接访问"的原则,就可以实现确定性。

所以在这个例子中,当等待超时的部分返回后,to通道就不再被任何人读取了。所以这个goroutine现在是无用的,有没有办法让它在不执行任何工作的情况下返回?

例如,一个读取文件的goroutine使用defer在完成后关闭文件,它能否"感知"到它应该发送数据的通道已经关闭,从而在不再读取任何数据的情况下返回?

我还想知道为什么select语句是"非确定性"的,如果第一个和第二个case都准备好了(对于非阻塞操作),我希望第一个case优先执行。我不会因此谴责它,但是有没有原因呢?这个实现是怎样的?

最后,goroutine是如何调度的?编译器是否会在每个指令之间插入某种"让步",以便正在运行的线程可以在不同的goroutine之间切换?我在哪里可以找到关于更底层的信息?

我知道Go宣称"你根本不需要担心这些",但我喜欢知道我所编写的代码实际上隐藏了什么(这可能是C++的一种思维方式),以及隐藏的原因。

英文:

Okay SO is warning me about a subjective title so please let me explain. Right now I'm looking at Go, I've read the spec, watched a few IO talks, it looks interesting but I have some questions.

One of my favourite examples was this select statement that listened to a channel that came from "DoAfter()" or something, the channel would send something at a given time from now.

Something like this (this probably wont work, pseudo-go if anything!)

to := Time.DoAfter(1000 * Time.MS)
select:
    case &lt;-to:
        return nil //we timed out
    case d := &lt;-waitingfor:
        return d

Suppose the thing we're waiting for happens really fast, so this function returns and isn't listening to to any more, what happens in DoAfter?

I like and know that you ought not test the channel, for example

if(chanToSendTimeOutOn.isOpen()) {
    chanToSendTimeOutOn&lt;-true
}

I like how channels sync places, with this for example it is possible that the function above could return after the isOpen() test but before the sending of true. I really am against the test, this avoids what channels do - hide locks and whatnot.

I've read the spec and seen the run time panics and recovery, but in this example where do we recover? Is the thing waiting to send the timeout a go routine or an "object" of sorts? I imagined this "object" which had a sorted list of things it had to send things to after given times, and that it'd just append TimeAfter requests to the queue in the right order and go through it. I'm not sure where that'd get an opportunity to actually recover.

If it spawned go-routines each with their own timer (managed by the run-time of course, so threads don't actually block for time) what then would get the chance to recover?

The other part of my question is about the lifetime of channels, I would imagine they're ref counted, well those able to read are ref-counted, so that if nothing anywhere holds a readable reference it is destroyed. I'd call this deterministic. For the "point-to-point" topologies you can form it will be if you stick towards Go's "send stuff via channels, don't access it"

So here for example, when the thing that wants a timeout returns the to channel is no longer read by anyone. So the go-routine is pointless now, is there a way to make it return without doing work?

Example:

File-reading go routine that has used defer to close the file when it is done, can it "sense" the channel it is supposed to send stuff to has closed, and thus return without reading any more?

I'd also like to know why the select statement is "nondeterministic" I'd have quite liked it if the first case took priority if the first and second are ready (for a non-blocking operation) - I wont condemn it for that, but is there a reason? What's the implementation of this?

Lastly, how are go-routines scheduled? Does the compiler add some sort of "yielding" every so many instructions, so a thread running will switch between different goroutines? Where can I find info on the lower level stuff?

I know Go touts that "you simply don't need to worry about this" but I like to know what things I write actually hide (that could be a C++ thing) and the reasons why.

答案1

得分: 19

如果向一个关闭的通道写入数据,你的程序将会发生 panic(参见 http://play.golang.org/p/KU7MLrFQSx 的示例)。你可以使用 recover 来捕获这个错误,但是在不知道要写入的通道是否打开的情况下,通常意味着程序中存在 bug。通道的发送方负责关闭通道,因此它应该知道当前的状态。如果有多个 goroutine 在通道上发送数据,那么它们应该协调关闭通道(例如使用 sync.WaitGroup)。

在你提出的 Time.DoAfter 的假设中,这将取决于通道是否有缓冲区。如果它是一个无缓冲的通道,那么向计时器通道写入数据的 goroutine 将会阻塞,直到有人从通道中读取数据。如果这从未发生,那么该 goroutine 将一直阻塞,直到程序完成。如果通道有缓冲区,发送操作将立即完成。在有人从通道中读取数据之前,该通道可能会被垃圾回收。

标准库的 time.After 函数的行为就是这样,它返回一个带有一个缓冲区的通道。

英文:

If you write to a closed channel, your program will panic (see http://play.golang.org/p/KU7MLrFQSx for example). You could potentially catch this error with recover, but being in a situation where you don't know whether the channel you are writing to is open is usually a sign of a bug in the program. The send side of the channel is responsible for closing it, so it should know the current state. If you have multiple goroutines sending on the channel, then they should coordinate in closing the channel (e.g. by using a sync.WaitGroup).

In your Time.DoAfter hypothetical, it would depend on whether the channel was buffered. If it was an unbuffered channel, then the goroutine writing to the timer channel would block until someone read from the channel. If that never happened, then the goroutine would remain blocked until the program completed. If the channel was buffered, the send would complete immediately. The channel could be garbage collected before anyone read from it.

The standard library time.After behaves this way, returning a channel with a one slot buffer.

huangapple
  • 本文由 发表于 2014年6月7日 18:00:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/24096026.html
匿名

发表评论

匿名网友

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

确定