我把我的问题归结为下面这个简单的例子。我正在调用一个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
读取,因此死锁。