3. 用 channel 实现 goroutine 间的基本通信
无缓冲 channel 的同步握手
无缓冲 channel 的容量为 0,发送与接收必须同时就绪才能完成一次传递。发送方执行 ch <- v 时会阻塞,直到有接收方执行 <-ch;反之,接收方先执行 <-ch 也会阻塞,直到有发送方送来数据。这种“双方碰面才放行”的行为就是同步通信。
完整可运行示例
下面的程序创建两个 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 中。