【问题标题】:Why does Go panic on writing to a closed channel?为什么 Go 在写入封闭通道时会感到恐慌?
【发布时间】:2016-04-26 04:03:27
【问题描述】:

为什么 Go 在写入已关闭的频道时会出现恐慌?

虽然可以使用value, ok := <-channel 习惯用法从通道中读取,因此可以测试 ok 的结果是否命中已关闭的通道:

// reading from closed channel

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("channel is closed\n")
        return
    }
    fmt.Printf("read %d from channel\n", i)
}

输出:

read 2 from channel
channel is closed
channel is closed

Playground上运行“从封闭通道读取”

写入可能已关闭的通道更加复杂,因为如果您只是在通道关闭时尝试写入,Go 会出现恐慌:

//writing to closed channel

package main

import (
    "fmt"
)

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("%v", r)
            fmt.Printf("write: error writing %d on channel: %v\n", i, err)
            return
        }

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

    out <- i // write on possibly closed channel

    return err
}

输出:

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

Playground上运行“写入封闭通道”

据我所知,没有更简单的习惯用法可以在不恐慌的情况下写入可能已关闭的频道。为什么不?读写之间的这种不对称行为背后的原因是什么?

【问题讨论】:

  • 我们怎么知道?在 google golang group 上提问,也许其中一位作者会回答你。我能想到一个原因。在生产者端关闭频道只是一个很好的设计。恐慌迫使您以这种方式设计您的应用程序。
  • 关闭通道是一个信号,这里将不再有值。写入已关闭的通道是程序错误,会出现恐慌。

标签: go concurrency channel goroutine panic


【解决方案1】:

来自Go Language Spec

对于通道 c,内置函数 close(c) 记录不再 值将在通道上发送。如果 c 是 a 则为错误 只接收通道。发送到或关闭关闭的通道会导致 运行时恐慌。关闭 nil 通道也会导致运行时恐慌。 在调用 close 之后,并且在任何先前发送的值已经被 收到,接收操作将返回零值 没有阻塞的通道类型。多值接收操作 返回接收到的值以及是否 频道已关闭。

如果你写到一个封闭的频道,你的程序会恐慌。如果你真的想这样做,你可能会catch this error with recover,但是在你不知道你正在写入的频道是否打开的情况下,通常是程序中存在错误的迹象。

一些引用:

这是一个动机:

一个通道“关闭”实际上只是一个特殊值的发送 渠道。这是一个特殊的价值,它承诺不再有更多的价值 被发送。尝试在通道上发送一个值后 关闭会恐慌,因为实际发送值会违反 关闭提供的保证。因为关闭只是一种特殊的 发送,在通道关闭后也是不允许的。

这是另一个:

频道关闭的唯一用途是向读者发出信号: 没有更多的价值来了。只有当有一个 单一价值来源,或当多个来源协调时。那里 不是一个合理的程序,其中多个 goroutine 关闭一个通道 没有沟通。这意味着多个 goroutine 会知道没有更多的价值可以发送——他们怎么能 确定如果他们不沟通?

(伊恩·兰斯·泰勒)

--

这是另一个:

关闭频道会将其作为资源释放。没有意义 关闭频道的次数比关闭文件的次数多 描述符多次,或释放一块分配的内存 多次。这样的行为意味着代码被破坏了,这就是为什么 关闭已关闭的频道会引发恐慌。

(罗伯·派克)

--

来源:Go design detail rationale question - channel close

【讨论】:

  • "但是处于不知道要写入的通道是否打开的情况通常表明程序中存在错误"。如果有多个并发写入同一个通道并且其中一个决定关闭通道而无需通知所有其他 goroutine 怎么办?
  • @Sreram 您可以拥有多个频道,而不是让多个作家进入同一个频道。然后关闭每个通道。在接收端,您可以在每个频道准备就绪时使用 select 从每个频道中进行选择。
猜你喜欢
  • 1970-01-01
  • 2019-03-18
  • 2010-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多