gpt4 book ai didi

go - 为什么在 defer 语句中关闭 channel 会出现 panic ?

转载 作者:行者123 更新时间:2023-12-01 22:42:13 24 4
gpt4 key购买 nike

在下面的示例中,go 例程将值泵入无缓冲 channel ,并且 main 函数对其进行迭代。

package main

import (
"fmt"
"strconv"
)

var chanStr chan string

func main() {
go pump()
fmt.Println("iterating ...")
for val := range chanStr {
fmt.Printf("fetched val: %s from channel\n", val)
}
}

func pump() {
defer close(chanStr)
chanStr = make(chan string)
for i := 1; i <= 5; i++ {
fmt.Printf("pumping seq %d into channel\n", i)
chanStr <- "val" + strconv.Itoa(i)
}
//close(chanStr)
}

该函数会出现以下输出:
iterating ...                                             
pumping seq 1 into channel
pumping seq 2 into channel
fetched val: val1 from channel

......

fetched val: val4 from channel
pumping seq 5 into channel
panic: close of nil channel

goroutine 5 [running]:
main.pump()
C:/personal/gospace/go-rules/test.go:26 +0x1a6
created by main.main
C:/personal/gospace/go-rules/test.go:11 +0x4e

但是,如果我注释 defer 语句并在 goroutine 中的 for 循环之后立即关闭 pump ,接收者不会 panic 。
这两种情况有什么区别?看起来 defer 在收到值之前关闭了 channel ,但常规关闭等待。

此外,当我使用竞赛检测器构建时,即使在常规关闭中,它也会标记潜在的竞赛条件(我无法每次都重新创建竞赛)。这是否意味着这两种方式都不适合优雅地关闭 channel ?

更新:
对于所有评论,我知道问题出在哪里。我必须在 main() 的第一行创建 channel 功能。但是,我在带有 go1.12 的 Windows 上运行,我观察到了这种行为。
显然我没有伪造输出。我一直在使用 defer 语句重新创建 panic ,甚至在我在 pump() 中的 for 循环之后立即关闭 channel 时发生 panic 。

最佳答案

您的代码非常活泼,以不同的方式:

  • 您有可能(事实上,很有可能)从您的 for val 中的 channel 开始阅读。循环,在 goroutine 实际初始化 channel 之前,导致死锁。
    iterating ...
    pumping seq 1 into channel
    fatal error: all goroutines are asleep - deadlock!

    事实上,这是我观察到的唯一行为,无论是在本地还是在操场上按原样执行您的代码。
  • 如果我添加延迟,
     fmt.Println("iterating ...")
    time.Sleep(10 * time.Millisecond) // Delay ensures the channel has been created
    for val := range chanStr {

    然后我确实观察到你注意到的行为:
    iterating ...
    pumping seq 1 into channel
    fetched val: val1 from channel
    pumping seq 2 into channel
    pumping seq 3 into channel
    fetched val: val2 from channel
    fetched val: val3 from channel
    pumping seq 4 into channel
    pumping seq 5 into channel
    fetched val: val4 from channel
    fetched val: val5 from channel
    panic: close of nil channel

    原因是您正在调用 close(chanStr)chanStr仍然是零。如果您调用 defer创建 channel 后:
    func pump() {
    chanStr = make(chan string)
    defer close(chanStr)

    你会解决这个问题的。

  • 要解决这两种竞争,您需要在调用 goroutine 之前初始化 channel 。完整代码:
    package main

    import (
    "fmt"
    "strconv"
    )

    var chanStr chan string

    func main() {
    chanStr = make(chan string)
    go pump(chanStr)
    fmt.Println("iterating ...")
    for val := range chanStr {
    fmt.Printf("fetched val: %s from channel\n", val)
    }
    }

    func pump(chanStr chan string) {
    defer close(chanStr)
    for i := 1; i <= 5; i++ {
    fmt.Printf("pumping seq %d into channel\n", i)
    chanStr <- "val" + strconv.Itoa(i)
    }
    }

    进一步说明问题是 defer close(chanStr)评估 chanStr立即(虽然它仍然是 nil ),考虑这个(不推荐!)替代解决方案:
    package main

    import (
    "fmt"
    "strconv"
    "time"
    )

    var chanStr chan string

    func main() {
    go pump()
    fmt.Println("iterating ...")
    time.Sleep(10 * time.Millisecond)
    for val := range chanStr {
    fmt.Printf("fetched val: %s from channel\n", val)
    }
    }

    func pump() {
    defer func() {
    close(chanStr)
    }()
    chanStr = make(chan string)
    for i := 1; i <= 5; i++ {
    fmt.Printf("pumping seq %d into channel\n", i)
    chanStr <- "val" + strconv.Itoa(i)
    }
    }

    在这种情况下,延迟函数是对 chanStr 的闭包。 , 所以 chanStr的评估延迟到实际执行。在这个版本中,当延迟函数执行时, chanStr不再是 nil,所以不用 panic 。

    关于go - 为什么在 defer 语句中关闭 channel 会出现 panic ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58356472/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com