Go Channel 写入数据超出其容量

Go Channel writes data beyond it's capacity

package main

import (
    "fmt"
)

func write(ch chan int) {
    for i := 0; i < 5; i++ {
        fmt.Println("avaliable", i)
        ch <- i
        fmt.Println("successfully wrote", i, "to ch")
    }

    close(ch)
}

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

    go write(ch)

    for v := range ch {
        fmt.Println("read value", v, "from ch")
    }
}

输出

avaliable 0
successfully wrote 0 to ch
avaliable 1
read value 0 from ch
read value 1 from ch
successfully wrote 1 to ch
avaliable 2
successfully wrote 2 to ch
avaliable 3
read value 2 from ch
read value 3 from ch
successfully wrote 3 to ch
avaliable 4
successfully wrote 4 to ch
read value 4 from ch

因为这是一个无缓冲的通道,它应该在数据写入后立即阻塞,直到另一个 goroutine 从同一通道读取数据。但它接受超出其容量的数据。

预期行为

package main

import (
    "fmt"
    "time"
)

func write(ch chan int) {
    for i := 0; i < 5; i++ {
        fmt.Println("avaliable", i)
        ch <- i
        fmt.Println("successfully wrote", i, "to ch")
    }

    close(ch)
}

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

    go write(ch)

    time.Sleep(time.Second)

    for v := range ch {
        fmt.Println("read value", v, "from ch")

        time.Sleep(time.Second)
    }
}

输出

avaliable 0
read value 0 from ch
successfully wrote 0 to ch
avaliable 1
read value 1 from ch
successfully wrote 1 to ch
avaliable 2
read value 2 from ch
successfully wrote 2 to ch
avaliable 3
read value 3 from ch
successfully wrote 3 to ch
avaliable 4
read value 4 from ch
successfully wrote 4 to ch

如果在整个代码中放置一些计时器,以便主 goroutine 在每次迭代之前被阻塞,它将按预期工作。

如果是这样的话,“休眠”版的输出就更乱了

您不能指望两个独立线程的输出一致,即使它们在某些时候是同步的。

您无法从输出中推断出任何内容,因为在“读取值”和“成功写入”Printfs 执行之间没有顺序保证。 (在通道发送之前发生的“可用”Printf 和相应通道接收之后发生的“读取值”Printf 之间有一个,您可以看到输出中从未违反该顺序)。

通道没有缓冲任何东西,因为它没有缓冲;只是在通道发送完成后,两个不同的 goroutines 运行 以不确定的顺序排列。有时发送方先打印“成功写入”消息,有时接收方先打印“读取值”消息。任何一个都不会“领先”超过一个值,因为它们仍然在通道发送上完全同步;他们只是争先恐后地打印他们的状态消息。

当您将 Sleep 调用添加到 main 时,它恰好使 goroutine 运行ning write 将始终被阻塞以等待发送下一个值,而一个 运行ning main 阻塞了对 Sleep 的调用。当计时器超时时,主协程被唤醒,并立即发现通道上有东西在等待它,抓住它,然后继续休眠,然后 write 协程被唤醒。通过放慢速度,您可以让调度程序以一致的顺序处理 运行 事情(尽管这仍然部分取决于运气);没有睡眠,一切都运行尽可能快,结果显然是随机的。