为什么信道接收顺序会导致/解决Golang的死锁



我把我的问题归结为下面这个简单的例子。我正在调用一个goroutine,它占用两个通道,并向每个通道发送一条消息。然后,我正试图进一步接收这些信息。然而,接收渠道的顺序很重要。如果我使用与发送消息相同的顺序,程序就会运行。如果我切换,它不会。

我本来希望goroutine独立于检索消息运行,允许我首先从任何我想接收的频道接收消息。

我可以通过每个goroutine(2个goroutine(向单个通道发送消息来解决这个问题。

有人能解释一下为什么这里有顺序依赖性,以及为什么两个单独的goroutine解决了这种依赖性吗?

package main
import "fmt"
func main() {
chanA := make(chan string)
chanB := make(chan string)
go func() {
chanA <- "el"
chanB <- "el"
}()
// if B is received before A, fatal error
// if A is received before B, completes
<-chanB
<-chanA
fmt.Println("complete")
}

您需要缓冲通道。缓冲通道在阻塞之前可以存储很多元素。

chanA := make(chan string, 1)
chanA <- "el" // This will not block
fmt.Println("Hello World")

当您在上面的缓冲通道上执行chanA <- "el"时,元素会被放入缓冲区,线程不会阻塞。如果你添加第二个元素,它就会阻塞,因为缓冲区中没有空间:

chanA := make(chan string, 1)
chanA <- "el"
chanA <- "el" // <- This will block, as the buffer is full

在您的示例中,您有一个缓冲区0。因此,对通道的第一次写入被阻止,需要另一个线程读取该值才能解除阻止。

https://go.dev/play/p/6GbsVW4d0Mg

chanA := make(chan string)
go func() {
time.Sleep(time.Second)
fmt.Println("Pop:", <-chanA) // Unblock the writer
}()
chanA <- "el"

额外知识

如果不希望线程阻塞,可以将通道插入包裹在选择中。这将确保在通道已满的情况下,应用程序不会死锁。解决这个问题的一个廉价方法是使用更大的缓冲区。。。

https://go.dev/play/p/kKR-lrCO4FX

select {
case chanA <- "el":
default:
return fmt.Errorf("value not written: %s", value)
}

goroutine就是这样工作的:

除非找到另一个从同一通道写入/读取的goroutine,否则goroutine将在通道上被阻止读取/写入。

请注意上述阻止报价中的读/写写/读

在您的情况下,您的anon goroutine(以go开始(等待在channelA上写入,直到它找到一个从channelA读取的goroutine。主goroutine等待从channelB读取,除非它找到从中读取的goroutine。

你可以这样想,除非go找到另一个从同一通道写入/读取的例程,否则在读取/写入通道后写入的任何行都不会被考虑。

所以,如果你改变读或写顺序,你就不会出现死锁,或者正如你所说的,另一个goroutine也会完成这项工作。

希望一切都清楚。

对未缓冲通道的写入或读取将被阻止,直到有goroutine可向该通道写入或从该通道读取。当goroutine写入a时,它将阻塞,直到主goroutine可以从a读取,但主goroutine也被阻塞,等待从b读取,因此死锁。

最新更新