Go通道的行为是否取决于消息是如何发送到通道中的?

huangapple go评论85阅读模式
英文:

Does go channel behavior change depending on how messages are sent to it?

问题

这两段代码为什么执行结果不同呢?

  1. 运行下面的代码会返回一个fatal error: all goroutines are asleep - deadlock!错误。
func main() {
    ch := make(chan int)
    ch <- 1
    fmt.Println(<-ch)
}
  1. 运行下面的代码会正确地返回2和3(每个值占一行)。
func main() {
    ch := make(chan int)
    go buffer(ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

func buffer(ch chan int) {
    ch <- 2
    ch <- 3
}

在第一段代码中,只有当ch被定义为带缓冲的通道时,才能正确执行。为什么?为什么buffer()函数中的ch可以接收两个值而不报错?

英文:

Why do these 2 pieces of code execute differently?

  1. Running the below code returns a fatal error: all goroutines are asleep - deadlock!error.
func main() {
	ch := make(chan int)
	ch &lt;- 1
	fmt.Println(&lt;-ch)
}
  1. Running the below code correctly returns 2 and 3 (each on its own line).
	ch := make(chan int)
	go buffer(ch)
	fmt.Println(&lt;-ch)
	fmt.Println(&lt;-ch)
}

func buffer(ch chan int) {
	ch &lt;- 2
	ch &lt;- 3
}

#1 executes correctly only when ch is defined as a buffered channel. Why? Why does ch in buffer() accept 2 values without complaining?

答案1

得分: 2

一个发送操作到一个无缓冲通道会阻塞,直到另一个goroutine从中读取。这就是为什么第一段代码会发生死锁:当你向通道发送数据时,没有其他的goroutine从中读取。

在第二种情况下,你有一个goroutine向通道发送两个值,而主goroutine与发送操作同时进行两次读取。通道并不接受两次发送,而是逐个进行,第一次发送操作被第一次读取解除阻塞,第二次发送操作被第二次读取解除阻塞。

英文:

A send operation to an unbuffered channel will block until another goroutine reads from it. That's why the first piece of code deadlocks: when you send to the channel, there are no other goroutines reading from it.

In the second case, you have a goroutine sending two values to the channel, and the main goroutine is reading twice from the channel concurrent with the sends. The channel is not accepting two sends, it is doing it one by one, the first send operation is unblocked by the first read, and the second send operation is unblocked by the second read.

答案2

得分: 0

在你的代码示例中,由于只有一个goroutine(即主goroutine),第3行和第4行永远不会执行,因此实际上是这样的:

ch := make(chan int)
ch <- 1               // 永远阻塞 - 因为没有人在读取
// fmt.Println(<-ch)
// fmt.Println(<-ch)

通过通道进行通信是为了在goroutine之间使用的 - 由于这里只有一个goroutine,它将永远阻塞。

正如你所看到的,缓冲通道在某种程度上有所帮助...

ch := make(chan int, 1) // 缓冲区大小为1

ch <- 1               // 正常工作
ch <- 2               // 永远阻塞

// 永远不会执行
// fmt.Println(<-ch)
// fmt.Println(<-ch)

...但这有点牵强,而且没有太多用处。

在你的第二个示例中,这是正确的模式,至少有两个协作的goroutine:

go buffer(ch) // 写入通道的goroutine

创建了一个专用的写入goroutine,使主goroutine能够实时成功读取这些写入的内容:

fmt.Println(<-ch)
fmt.Println(<-ch)
英文:

In your code example, since there is only one goroutine (the main goroutine) lines 3 & 4 are never reached, so its effectively:

ch := make(chan int)
ch &lt;- 1               // blocks forever - as no one is reading
// fmt.Println(&lt;-ch)
// fmt.Println(&lt;-ch)

communication via channels is meant to be used across goroutines - since there is only one goroutine here it will forever block.

Buffering the channel, as you've seen, helps somewhat...

ch := make(chan int, 1) // buffer of one

ch &lt;- 1               // works
ch &lt;- 2               // blocks forever

// never reached
// fmt.Println(&lt;-ch)
// fmt.Println(&lt;-ch)

... but is somewhat contrived & not very useful.

In your second example, this is the correct pattern, having at least 2 goroutines cooperating:

go buffer(ch) // goroutine that writes to the channel

creates a dedicated writer goroutine, freeing up the main goroutine to successful read any of these writes in realtime:

fmt.Println(&lt;-ch)
fmt.Println(&lt;-ch)

huangapple
  • 本文由 发表于 2023年4月22日 00:24:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76075065.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定