Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/go/7.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Go 为什么在延迟语句中关闭通道会引起恐慌?_Go_Goroutine - Fatal编程技术网

Go 为什么在延迟语句中关闭通道会引起恐慌?

Go 为什么在延迟语句中关闭通道会引起恐慌?,go,goroutine,Go,Goroutine,在下面的示例中,go例程将值泵送到无缓冲通道中,主函数对其进行迭代 主程序包 进口( “fmt” “strconv” ) var chanStr chan字符串 func main(){ 加油泵() fmt.Println(“迭代…”) 对于val:=range chanStr{ fmt.Printf(“从通道\n获取的val:%s”,val) } } func泵(){ 延迟关闭(chanStr) chanStr=制造(成串) 对于i:=1;i您发布的代码有一个死锁场景。正如Flimzy指出的,

在下面的示例中,go例程将值泵送到无缓冲通道中,主函数对其进行迭代

主程序包
进口(
“fmt”
“strconv”
)
var chanStr chan字符串
func main(){
加油泵()
fmt.Println(“迭代…”)
对于val:=range chanStr{
fmt.Printf(“从通道\n获取的val:%s”,val)
}
}
func泵(){
延迟关闭(chanStr)
chanStr=制造(成串)

对于i:=1;i您发布的代码有一个死锁场景。正如Flimzy指出的,您可能没有发布相同的代码

以下是应该可以使用的更新代码:

package main

import (
    "fmt"
    "strconv"
)

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(ch chan string) {
    defer close(ch)

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

主程序包
进口(
“fmt”
“strconv”
)
func main(){
chanStr:=make(chan字符串)
go泵(chanStr)
fmt.Println(“迭代…”)
对于val:=range chanStr{
fmt.Printf(“从通道\n获取的val:%s”,val)
}
}
func泵(ch chan管柱){
延迟关闭(ch)

对于i:=1;i主go例程可能会在创建之前从通道中读取数据。这就是您的数据竞争

应在开始围棋程序之前创建通道


修复:

您的代码非常快速,有不同的方式:

  • 在goroutine实际初始化通道之前,您有可能(事实上,很可能)在
    for val
    循环中开始读取通道,从而导致死锁

    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

    func pump() {
        chanStr = make(chan string)
        defer close(chanStr)
    
    你会解决那个问题的

  • 要解决这两个争用,您需要在调用goroutine之前初始化通道。完整代码:

    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)
        }
    }
    

    在这种情况下,延迟函数是对
    chanStr
    的闭包,因此
    chanStr
    的计算会延迟到实际执行。在这个版本中,当延迟函数执行时,
    chanStr
    不再为零,因此不会出现恐慌。

    比赛是主go例程从一个将被改变的chan值中读取的地方d由后台go例程执行。提前创建通道。由于通道只是一个引用,而
    泵()
    保持对该引用的关闭,当调用
    延迟关闭(chanStr)
    时,它不应该是
    nil
    通道,对吗?尽管通道不是引用。以及
    泵()
    是否为闭包无关紧要。
    close(chanStr)
    会立即计算,即使函数退出后才会执行
    close
    函数本身。我用另一个示例更新了答案,以说明问题在于
    chanStr
    的早期计算。“发生这种情况是因为通道在主函数可以使用数据之前关闭”--不,这是不准确的。发生这种情况是因为
    延迟关闭(chanStr)
    是在定义chanStr
    之前被调用的。您会注意到,在失败的运行中,数据是在死锁之前从通道中消耗的。这部分与他发布的代码无关,这是为什么从关闭的通道读取会导致死锁的答案。这部分是在我运行他的代码并注意到死锁之前添加的。Readin但是,通道中的g不会引起恐慌。关闭通道会引起恐慌。OP自身的输出显示通道中的成功读取。我认为在帖子中提到了类似的内容,但你是对的。我得到了更正。顺便说一句,你的解决方案会解决问题…只是不是因为你解释的原因:)这是一个race、 但不是OP询问的那一个。:)这个特定的竞争导致死锁,而不是关闭一个零通道。@Flimzy true。但是我在go例程之前创建通道的修复方法解决了这两个竞争条件。不值得扭曲代码来处理可能更改的通道值。相反,修复代码,使通道值永远不会更改。是的,这与我在回答中提出的解决方案相同。
    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)
        }
    }
    
    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)
        }
    }