为什么在向已关闭的通道写入时Go会发生panic?

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

Why does Go panic on writing to a closed channel?

问题

为什么在向关闭的通道写入时Go会发生panic?

虽然可以使用value, ok := <-channel的方式从通道中读取数据,并且可以通过检查ok的结果来判断是否读取到了关闭的通道:

// 从关闭的通道读取

package main

import "fmt"

func main() {
    ch := make(chan int, 1)
    ch <- 2
    close(ch)
    
    read(ch)
    read(ch)
    read(ch)
}

func read(ch <-chan int) {
    i, ok := <-ch    
    if !ok {
        fmt.Printf("通道已关闭\n")
        return
    }
    fmt.Printf("从通道中读取到 %d\n", i)
}

输出结果:

从通道中读取到 2
通道已关闭
通道已关闭

Playground上运行"从关闭的通道读取"。

然而,向可能已关闭的通道写入数据更加复杂,因为如果在通道关闭后尝试写入数据,Go会发生panic:

// 向关闭的通道写入

package main

import (
    "fmt"
)

func main() {
    output := make(chan int, 1) // 创建通道
    write(output, 2)
    close(output) // 关闭通道
    write(output, 3)
    write(output, 4)
}

// 如何向可能已关闭的通道写入数据
func write(out chan int, i int) (err error) {

    defer func() {
        // 从由向关闭的通道写入数据引起的panic中恢复
        if r := recover(); r != nil {
            err = fmt.Errorf("%v", r)
            fmt.Printf("写入数据时发生错误:%d,错误信息:%v\n", i, err)
            return
        }

        fmt.Printf("成功向通道中写入数据:%d\n", i)
    }()

    out <- i // 向可能已关闭的通道写入数据

    return err
}

输出结果:

成功向通道中写入数据:2
写入数据时发生错误:3,错误信息:send on closed channel
写入数据时发生错误:4,错误信息:send on closed channel

Playground上运行"向关闭的通道写入"。

据我所知,目前没有更简单的方式来向可能已关闭的通道写入数据而不发生panic。为什么会这样?为什么在读取和写入之间存在这种不对称的行为?

英文:

Why does Go panic on writing to a closed channel?

While one can use the value, ok := &lt;-channel idiom for reading from channels, and thus the ok result can be tested for hitting a closed channel:

// reading from closed channel

package main

import &quot;fmt&quot;

func main() {
	ch := make(chan int, 1)
	ch &lt;- 2
	close(ch)
	
	read(ch)
	read(ch)
	read(ch)
}

func read(ch &lt;-chan int) {
	i,ok := &lt;- ch	
	if !ok {
		fmt.Printf(&quot;channel is closed\n&quot;)
		return
	}
	fmt.Printf(&quot;read %d from channel\n&quot;, i)
}

Output:

read 2 from channel
channel is closed
channel is closed

Run "reading from closed channel" on Playground

Writing to a possibly closed channel is more convoluted, because Go will panic if you simply try to write when the channel is closed:

//writing to closed channel

package main

import (
	&quot;fmt&quot;
)

func main() {
	output := make(chan int, 1) // create channel
	write(output, 2)
	close(output) // close channel
	write(output, 3)
	write(output, 4)
}

// how to write on possibly closed channel
func write(out chan int, i int) (err error) {

	defer func() {
		// recover from panic caused by writing to a closed channel
		if r := recover(); r != nil {
			err = fmt.Errorf(&quot;%v&quot;, r)
			fmt.Printf(&quot;write: error writing %d on channel: %v\n&quot;, i, err)
			return
		}

		fmt.Printf(&quot;write: wrote %d on channel\n&quot;, i)
	}()

	out &lt;- i // write on possibly closed channel

	return err
}

Output:

write: wrote 2 on channel
write: error writing 3 on channel: send on closed channel
write: error writing 4 on channel: send on closed channel

Run "writing to closed channel" on Playground

As far as I know, there is not a simpler idiom for writing into a possibly closed channel without panicking. Why not? What is the reasoning behind such an asymmetric behavior between read and write?

答案1

得分: 33

Go语言规范中可以得知:

对于一个通道c,内置函数close(c)表示不会再向该通道发送更多的值。如果c是一个只接收通道,那么这将是一个错误。向已关闭的通道发送数据或关闭一个已关闭的通道都会导致运行时恐慌。关闭空通道也会导致运行时恐慌。在调用close之后,并且在接收到之前发送的所有值之后,接收操作将返回该通道类型的零值,而不会阻塞。多值接收操作将返回接收到的值以及通道是否关闭的指示。

如果你向一个已关闭的通道写入数据,程序将会发生恐慌。如果你真的想要捕获这个错误,你可以使用recover来实现,但是在不知道你要写入的通道是否打开的情况下,通常意味着程序中存在bug。

以下是一些引用:

这是一个动机:

通道的“关闭”实际上只是在通道上发送一个特殊值。这是一个特殊的值,承诺不会再发送更多的值。在通道关闭后尝试发送值将会导致恐慌,因为实际发送该值将违反关闭提供的保证。由于关闭只是一种特殊类型的发送,因此在通道关闭后也不允许关闭通道。

这是另一个:

通道关闭的唯一用途是向读取器发出没有更多值的信号。只有在存在单个值源或多个源进行协调时才有意义。在没有通信的情况下,没有合理的程序会有多个goroutine关闭通道。这将意味着多个goroutine将知道没有更多的值要发送-如果它们不进行通信,它们怎么能确定呢?

(Ian Lance Taylor)

--

这是另一个:

关闭通道会释放它作为资源的使用。多次关闭通道与多次关闭文件描述符或多次释放分配的内存块一样没有意义。这样的操作意味着代码有问题,这就是为什么关闭一个已关闭的通道会触发恐慌的原因。

(Rob Pike)

--

来源:Go设计详细理由问题-通道关闭

英文:

From the Go Language Spec:

> For a channel c, the built-in function close(c) records that no more
> values will be sent on the channel. It is an error if c is a
> receive-only channel. Sending to or closing a closed channel causes a
> run-time panic. Closing the nil channel also causes a run-time panic.
> After calling close, and after any previously sent values have been
> received, receive operations will return the zero value for the
> channel's type without blocking. The multi-valued receive operation
> returns a received value along with an indication of whether the
> channel is closed.

If you write to a closed channel, your program will panic. You could potentially catch this error with recover if you really want to do that, 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.

Some quotes:

>Here is a motivation:
>
>A channel "close" is really just a send of a special value on a
channel. It is a special value that promises that no more values will
be sent. Attempting to send a value on a channel after it has been
closed will panic, since actually sending the value would violate the
guarantee provided by close. Since a close is just a special kind of
send, it is also not permitted after the channel has been closed.
>
>Here is another:
>
>The only use of channel close is to signal to the reader that there
are no more values to come. That only makes sense when there is a
single source of values, or when multiple sources coordinate. There
is no reasonable program in which multiple goroutines close a channel
without communicating. That would imply that multiple goroutines
would know that there are no more values to send--how could they
determine that if they don't communicate?
>
>(Ian Lance Taylor)

--

>Here is another:
>
>Closing a channel releases it as a resource. It makes no more sense to
close a channel multiple times than it makes to close a file
descriptor multiple times, or free a block of allocated memory
multiple times. Such actions imply the code is broken, which is why
closing a closed channel triggers a panic.
>
> (Rob Pike)

--

Source: Go design detail rationale question - channel close

答案2

得分: 0

Go通道被设计为1个写入者和多个读取者。因此,写入者在完成写入后必须创建、共享和关闭通道。在某种程度上,写入者拥有该通道。虽然你可以在单个通道上有多个写入者(但不建议这样做)。

英文:

Go channels are designed for 1 writer, and multiple readers. Thus the writer has to create, share, and close the channel once is done writing. In a way, the writer owns the channel. Although you could have multiple writers on a single channel (this is not advised/recommended).

huangapple
  • 本文由 发表于 2016年1月20日 18:45:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/34897843.html
匿名

发表评论

匿名网友

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

确定