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 发送操作的条件。

于是:

  1. 第一个 goroutine 从阻塞状态被唤醒
  2. 值 4 被传递给第二个 goroutine
  3. 两个 goroutine 都继续执行后续代码 2

通道内部实现

从 Go 的源代码来看,无缓冲通道内部维护了两个等待队列:

  • recvq:等待接收数据的 goroutine 队列
  • sendq:等待发送数据的 goroutine 队列

在你的例子中:

  1. 第一个 goroutine 执行 ch <- 4 时,由于没有接收者,它被包装成一个对象放入 sendq 队列并阻塞
  2. 当第二个 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