Go语言 缓冲通道并关闭

1l5u6lss  于 2022-12-07  发布在  Go
关注(0)|答案(2)|浏览(191)

我有一段代码,我试图根据close调用和位置来理解它

func main() {
    ch := make(chan int, 2)

    go func(ch chan int) {
        for i := 1; i <= 5; i++ {
            ch <- i
            fmt.Println("Func goroutine sends data: ", i)
        }
        //Pos1 - Works perfectly
        //close(ch)
    }(ch)

    fmt.Println("Main goroutine sleeps 2 seconds")
    time.Sleep(time.Second * 2)

    fmt.Println("Main goroutine begins receiving data")
    //Pos2 - Puts in only 2 ints 1 and 2 and then prints only that
    //close(ch)
    for d := range ch {
        fmt.Println("Main goroutine received data:", d)
    }
    //Pos3 - Throws fatal error
    close(ch)
}

我一直在努力理解和阅读这方面的博客,但仍然不能理解一些事情
1.当我把close放在Pos1时,它工作得很好。但是我不知道为什么它工作。缓冲区在任何给定的时间不能容纳超过2个元素,所以当2个元素被写入时,循环将阻塞,直到主路由进行读取。但是我想在缓冲通道上做一个范围,range函数必须事先知道要迭代多少个元素,并且对于该通道必须关闭。为什么close在这个位置工作?
1.当我把它放在位置2时,它只打印了2个元素,这是有意义的,但为什么for loop在试图向关闭的通道写入更多元素时没有抛出异常?
1.当我在Pos3结束时,我得到了一个异常fatal error: all goroutines are asleep - deadlock!,尽管所有5个int都被打印出来了。为什么?

hs1ihplo

hs1ihplo1#

但我认为要在缓冲通道上执行范围,范围函数必须事先知道要迭代多少个元素,并且必须关闭该通道。
这种假设是错误的,是所有误解的根源。
通道范围的行为在Go Spec:https://golang.org/ref/spec#For_statements
对于通道,产生的迭代值是在通道上发送的连续值,直到通道关闭。如果通道为nil,范围表达式将永远阻塞。
在计算for语句时不需要关闭通道,并且该语句不需要知道元素的数量。
所以,在你的代码中,当你把close放在Pos 1时,这确实是正确的方法;当你把它放在Pos 3时,for循环等待通道关闭,这只能在for循环本身之后发生,所以这是一个死锁。
close放在Pos 2中是有缺陷的,而且行为有点棘手。它 * 可能 * 引发错误,但也可能只输出两个数字。这是因为当通道在for循环之前关闭时,循环可以无阻塞地运行,然后main()返回。当main()返回时,Go程序结束,是否会引发错误完全取决于进程间的调度器是否切换到goroutine,但这并不能保证。

weylhg0b

weylhg0b2#

修改代码片段,添加一些peppered print语句到@leaf bebop的优秀答案中,以澄清发生了什么。
{1}{2}位置{3} 2 {4},但让主电源稍等{5}{6}

func main() {
    ch := make(chan int, 2)

    go func(ch chan int) {
        for i := 1; i <= 5; i++ {
            ch <- i
            fmt.Println("Func goroutine sends data: ", i)
        }
        fmt.Println("goR work is done")
    }(ch)

    fmt.Println("Main goroutine sleeps 2 seconds")
    time.Sleep(time.Second * 2)

    fmt.Println("Main goroutine begins receiving data")
    close(ch)
    for d := range ch {
        fmt.Println("Main goroutine received data:", d)
    }

    fmt.Println("Main waiting, will the other goR panic?")
    time.Sleep(time.Second * 10)
}

Main goroutine sleeps 2 seconds
Func goroutine sends data:  1
Func goroutine sends data:  2
Main goroutine begins receiving data
Main goroutine received data: 1
panic: send on closed channel

goroutine 6 [running]:
main.main.func1(0x0?)
    /tmp/sandbox3746599466/prog.go:15 +0x3b
created by main.main
    /tmp/sandbox3746599466/prog.go:13 +0x85

Program exited.

因此,如果有足够的时间(主例程没有退出),调度程序返回到go例程,在那里我们写入一个关闭的(由主例程关闭的)通道- Panic!

位置2,但仅发送2个值,即不写入关闭的通道

func main() {
    ch := make(chan int, 2)

    go func(ch chan int) {
        // Send only 2 values
        for i := 1; i <= 2; i++ {
            ch <- i
            fmt.Println("Func goroutine sends data: ", i)
        }
        fmt.Println("goR work is done")
    }(ch)

    fmt.Println("Main goroutine sleeps 2 seconds")
    time.Sleep(time.Second * 2)

    fmt.Println("Main goroutine begins receiving data")
    close(ch)
    for d := range ch {
        fmt.Println("Main goroutine received data:", d)
    }

    fmt.Println("Main waiting, will the other goR panic?")
    time.Sleep(time.Second * 10)
    fmt.Println("Main exiting")
}

Main goroutine sleeps 2 seconds
Func goroutine sends data:  1
Func goroutine sends data:  2
goR work is done
Main goroutine begins receiving data
Main goroutine received data: 1
Main goroutine received data: 2
Main waiting, will the other goR panic?
Main exiting

Program exited.

没有恐慌,永远没有旋转。主程序只是退出。
{1}{2}位置{3} 3 {4},检查总管是否畅通?{5}{6}

func main() {
    ch := make(chan int, 2)

    go func(ch chan int) {
        for i := 1; i <= 5; i++ {
            ch <- i
            fmt.Println("Func goroutine sends data: ", i)

        }
        fmt.Println("Routine done with work")
    }(ch)

    fmt.Println("Main goroutine sleeps 2 seconds")
    time.Sleep(time.Second * 2)

    fmt.Println("Main goroutine begins receiving data")
    for d := range ch {
        fmt.Println("Main goroutine received data:", d)
    }

    fmt.Println("Have I closed this channel?")
    close(ch)
    fmt.Println("I have")
}

Main goroutine sleeps 2 seconds
Func goroutine sends data:  1
Func goroutine sends data:  2
Main goroutine begins receiving data
Main goroutine received data: 1
Main goroutine received data: 2
Main goroutine received data: 3
Func goroutine sends data:  3
Func goroutine sends data:  4
Func goroutine sends data:  5
Routine done with work
Main goroutine received data: 4
Main goroutine received data: 5
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    /tmp/sandbox3301646152/prog.go:26 +0x194

Program exited.

Main甚至没有关闭通道,它在range循环中被阻塞了。它将永远停留在这里,直到它得到一个值,或者有人关闭了通道。我们没有任何其他例程来做这件事,所以死锁。

相关问题