我正在学习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((",则缓冲通道应阻塞。但这没有发生,为什么?
其他输出是合理的。
我令人困惑的结果的图像
导致此问题的事件顺序如下:
- 触发执行例程。
- 将
2
写入通道。通道的容量现已耗尽。 - 从通道读取并输出结果。
- 将
1
写入通道。 - 从通道读取并输出结果。
导致此死锁的事件顺序如下:
- 触发执行例程。
- 将
1
写入通道。通道的容量现已耗尽。 - 将
2
写入通道。由于通道的缓冲区已满,因此会阻塞。
您提供的输出似乎表明 goroutine 首先完成并且程序没有死锁,这与上述两种情况相矛盾。以下是发生的情况:
- 触发执行例程。
- 将
2
写入通道。 - 从频道中读取
2
。 - 将
1
写入通道。 - 输出
In f()
. - 输出从通道接收的
2
。 - 从频道读取
1
。 - 输出从通道接收
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 分钟