Golang 通道阻塞机制解析
这个问题涉及到 Go 语言中通道(channel)的阻塞机制,特别是无缓冲通道的工作s原理。我来详细解释一下。
无缓冲通道的阻塞特性
在你的代码中,创建了一个无缓冲通道:ch := make(chan int, 0)
。对于无缓冲通道,Go 官方文档中表述为:
默认情况下,发送和接收操作会阻塞,直到另一方准备好。
这句话是理解整个过程的关键。具体到你的代码:
func main() {
ch := make(chan int, 0)
fmt.Printf("Begin Time: %v\n", time.Now().Unix())
go func() {
ch <- 4
fmt.Printf("Current Time: %v, and write 4 successfully\n", time.Now().Unix())
}()
time.Sleep(2 * time.Second)
go func() {
a := <-ch
fmt.Printf("Current Time: %v, and read %d successfully\n", time.Now().Unix(), a)
}()
time.Sleep(1 * time.Second)
}
上面这段代码中
go func() {
ch <- 4 // 发送操作
fmt.Printf("Current Time: %v, and write 4 successfully\n", time.Now().Unix())
}()
这个 goroutine 在执行 ch <- 4
时会立即阻塞,因为此时没有任何 goroutine 准备接收这个值。
解除阻塞的条件
2秒后,你启动了第二个 goroutine:
go func() {
a := <-ch // 接收操作
fmt.Printf("Current Time: %v, and read %d successfully\n", time.Now().Unix(), a)
}()
当执行到 a := <-ch
时,这个 goroutine 就变成了一个”准备接收数据的 goroutine”,此时满足了第一个 goroutine 发送操作的条件。
于是:
- 第一个 goroutine 从阻塞状态被唤醒
- 值 4 被传递给第二个 goroutine
- 两个 goroutine 都继续执行后续代码 2
通道内部实现
从 Go 的源代码来看,无缓冲通道内部维护了两个等待队列:
recvq
:等待接收数据的 goroutine 队列sendq
:等待发送数据的 goroutine 队列
在你的例子中:
- 第一个 goroutine 执行
ch <- 4
时,由于没有接收者,它被包装成一个对象放入sendq
队列并阻塞 - 当第二个 goroutine 执行
a := <-ch
时,运行时系统发现sendq
不为空,就会从中取出一个等待的 goroutine(即第一个 goroutine),让数据直接从发送者传递到接收者,然后唤醒发送者
func main() {
ch := make(chan int, 0)
fmt.Printf("Begin Time: %v\n", time.Now().Unix())
go func() {
ch <- 4
fmt.Printf("Current Time: %v, and write 4 successfully\n", time.Now().Unix())
}()
time.Sleep(2 * time.Second)
go func() {
a := <-ch
fmt.Printf("Current Time: %v, and read %d successfully\n", time.Now().Unix(), a)
}()
time.Sleep(1 * time.Second)
}
上面代码的结果如下:
准备发送数据
准备接收数据
接收到数据: 42
发送完成
Process finished with the exit code 0