为什么接收 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/writewrite/read

在你的例子中,你的匿名 goroutine(你用 go 开始)等待写入 channelA,直到它找到一个从 channelA 读取的 goroutine。 主 goroutine 等待从 channelB 读取,除非它找到一个从它读取的 goroutine。

你可以这样想,在 read/write 之后写到频道的任何行都不会被考虑,除非 go 找到另一个来自同一频道的 write/read 例程。

因此,如果您更改读取或写入顺序,则不会出现死锁,或者正如您所说,另一个 goroutine 也会完成这项工作。

希望清楚。