3. 用 channel 实现 goroutine 间的基本通信

无缓冲 channel 的同步握手

无缓冲 channel 的容量为 0,发送与接收必须同时就绪才能完成一次传递。发送方执行 ch <- v 时会阻塞,直到有接收方执行 <-ch;反之,接收方先执行 <-ch 也会阻塞,直到有发送方送来数据。这种“双方碰面才放行”的行为就是同步通信。

发送方 G1 channel 接收方 G2 ch <- 42 阻塞等待 v := <-ch 阻塞等待 数据 42 传递 继续执行 打印 42 接收完成信号 无缓冲 channel:发送与接收必须同时就绪,否则阻塞

完整可运行示例

下面的程序创建两个 goroutine:sender 向无缓冲 channel 发送整数 42,receiver 从 channel 接收并打印。主 goroutine 通过 sync.WaitGroup 等待两者结束。

package main

import (
	"fmt"
	"sync"
)

func main() {
	ch := make(chan int) // 无缓冲 channel
	var wg sync.WaitGroup

	// 发送方 goroutine
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("sender: 准备发送 42")
		ch <- 42 // 阻塞,直到 receiver 就绪
		fmt.Println("sender: 42 已发送")
	}()

	// 接收方 goroutine
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("receiver: 准备接收")
		v := <-ch // 阻塞,直到 sender 就绪
		fmt.Printf("receiver: 收到 %d\n", v)
	}()

	wg.Wait()
	fmt.Println("main: 通信完成")
}

运行结果与阻塞观察

典型输出如下(goroutine 调度顺序可能略有不同,但关键同步点一致):

sender: 准备发送 42
receiver: 准备接收
receiver: 收到 42
sender: 42 已发送
main: 通信完成
观察要点:如果注释掉接收方的 <-ch,发送方会永久阻塞在 ch <- 42,程序因所有 goroutine 都阻塞而触发 fatal error: all goroutines are asleep - deadlock!。这正是无缓冲 channel 同步特性的直接体现。

关键参数与行为对照

操作无缓冲 channel 行为阻塞条件
ch <- v等待接收方就绪后传递无接收方时阻塞
<-ch等待发送方就绪后取值无发送方时阻塞
双方同时就绪数据直接交接,不经过缓冲区不阻塞
仅一方就绪就绪方阻塞等待另一方阻塞
常见错误:在同一个 goroutine 中先发送再接收(ch <- 1; <-ch)会立即死锁,因为发送操作阻塞后永远等不到接收方。无缓冲 channel 的发送和接收必须位于不同的 goroutine 中。