为什么接收 causing/resolving 的频道顺序在 Golang 中会出现死锁?
Why is the order of channels receiving causing/resolving a deadlock in Golang?
我已将我的问题归结为下面这个简单的示例。我正在调用一个 goroutine,它采用两个通道并向每个通道发送一条消息。然后我试图进一步接收这些消息。但是,频道接收的顺序很重要。如果我使用与发送消息相同的顺序,程序 运行s。如果我切换,它不会。
我本来希望 goroutine 运行 独立于检索消息,允许我从我想先接收的任何渠道接收。
我可以通过向每个 goroutine(2 个 goroutine)的单个通道发送消息来解决这个问题。
有人可以解释为什么这里存在顺序依赖以及为什么 2 个独立的 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")
}
对无缓冲通道的写入或读取将阻塞,直到有 goroutine 写入或读取该通道。当 goroutine 写入 a
时,它会阻塞直到 main goroutine 可以从 a
读取,但是 main goroutine 也被阻塞等待从 b
读取,因此死锁。
]
您需要缓冲频道。缓冲通道在阻塞之前可以存储如此多的元素。
chanA := make(chan string, 1)
chanA <- "el" // This will not block
fmt.Println("Hello World")
当您在上面的 buffered 通道上执行 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"
额外知识
如果您不希望线程阻塞,可以将通道插入包裹在 select 中。这将确保如果通道已满,您的应用程序不会死锁。解决此问题的一种廉价方法是更大的缓冲区...
https://go.dev/play/p/kKR-lrCO4FX
select {
case chanA <- "el":
default:
return fmt.Errorf("value not written: %s", value)
}
goroutine 是这样工作的:
a goroutine will be blocked read/write on a channel unless if find another goroutine which write/read from the same channel.
注意上面屏蔽引用中的read/write和write/read
在你的例子中,你的匿名 goroutine(你用 go
开始)等待写入 channelA
,直到它找到一个从 channelA
读取的 goroutine。
主 goroutine 等待从 channelB 读取,除非它找到一个从它读取的 goroutine。
你可以这样想,在 read/write 之后写到频道的任何行都不会被考虑,除非 go 找到另一个来自同一频道的 write/read 例程。
因此,如果您更改读取或写入顺序,则不会出现死锁,或者正如您所说,另一个 goroutine 也会完成这项工作。
希望清楚。
我已将我的问题归结为下面这个简单的示例。我正在调用一个 goroutine,它采用两个通道并向每个通道发送一条消息。然后我试图进一步接收这些消息。但是,频道接收的顺序很重要。如果我使用与发送消息相同的顺序,程序 运行s。如果我切换,它不会。
我本来希望 goroutine 运行 独立于检索消息,允许我从我想先接收的任何渠道接收。
我可以通过向每个 goroutine(2 个 goroutine)的单个通道发送消息来解决这个问题。
有人可以解释为什么这里存在顺序依赖以及为什么 2 个独立的 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")
}
对无缓冲通道的写入或读取将阻塞,直到有 goroutine 写入或读取该通道。当 goroutine 写入 a
时,它会阻塞直到 main goroutine 可以从 a
读取,但是 main goroutine 也被阻塞等待从 b
读取,因此死锁。
您需要缓冲频道。缓冲通道在阻塞之前可以存储如此多的元素。
chanA := make(chan string, 1)
chanA <- "el" // This will not block
fmt.Println("Hello World")
当您在上面的 buffered 通道上执行 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"
额外知识
如果您不希望线程阻塞,可以将通道插入包裹在 select 中。这将确保如果通道已满,您的应用程序不会死锁。解决此问题的一种廉价方法是更大的缓冲区...
https://go.dev/play/p/kKR-lrCO4FX
select {
case chanA <- "el":
default:
return fmt.Errorf("value not written: %s", value)
}
goroutine 是这样工作的:
a goroutine will be blocked read/write on a channel unless if find another goroutine which write/read from the same channel.
注意上面屏蔽引用中的read/write和write/read
在你的例子中,你的匿名 goroutine(你用 go
开始)等待写入 channelA
,直到它找到一个从 channelA
读取的 goroutine。
主 goroutine 等待从 channelB 读取,除非它找到一个从它读取的 goroutine。
你可以这样想,在 read/write 之后写到频道的任何行都不会被考虑,除非 go 找到另一个来自同一频道的 write/read 例程。
因此,如果您更改读取或写入顺序,则不会出现死锁,或者正如您所说,另一个 goroutine 也会完成这项工作。
希望清楚。