为什么这个缓冲通道在我的代码中没有阻塞?

  • 本文关键字:代码 缓冲 通道 go
  • 更新时间 :
  • 英文 :


我正在学习Go语言。有人可以在这里解释输出吗?

package main
import "fmt"
var c = make(chan int, 1)
func f() {
c <- 1
fmt.Println("In f()")
}
func main() {
go f()
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}

输出:

In f()
2
1
Process finished with exit code 0

为什么"In f(("出现在"2"之前?如果在"2"之前打印"In f((",则缓冲通道应阻塞。但这没有发生,为什么?

其他输出是合理的。

我令人困惑的结果的图像

导致此问题的事件顺序如下:

  1. 触发执行例程。
  2. 2写入通道。通道的容量现已耗尽。
  3. 从通道读取并输出结果。
  4. 1写入通道。
  5. 从通道读取并输出结果。

导致此死锁的事件顺序如下:

  1. 触发执行例程。
  2. 1写入通道。通道的容量现已耗尽。
  3. 2写入通道。由于通道的缓冲区已满,因此会阻塞。

您提供的输出似乎表明 goroutine 首先完成并且程序没有死锁,这与上述两种情况相矛盾。以下是发生的情况:

  1. 触发执行例程。
  2. 2写入通道。
  3. 从频道中读取2
  4. 1写入通道。
  5. 输出In f().
  6. 输出从通道接收的2
  7. 从频道读取1
  8. 输出从通道接收1

请记住,除非以编程方式强制执行 goroutine,否则您对 goroutines 的调度没有任何保证。当你启动一个 goroutine,它未定义该 goroutine的第一个代码何时实际执行,以及在此之前启动代码的进展程度。请注意,由于您的代码依赖于特定的事件顺序,因此它被该定义所破坏,只是明确地说。

此外,在程序的任何时候,调度程序都可以决定在不同的 goroutines 之间切换。即使是单线fmt.Printtln(<-c)也由多个步骤组成,在每个步骤之间也可以进行切换。

要重现块,您必须多次运行该代码,这是使用test最简单的方法。你没有运气的原因。但不能保证:

var c = make(chan int, 1)
func f() {
c <- 1
fmt.Println("In f()")
}
func TestF(t *testing.T) {
go f()
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}

并使用命令运行:

go test -race -count=1000 -run=TestF -timeout=4s

其中count测试次数。它向我再现了阻塞。 提供超时以不等待默认 10 分钟

最新更新