讲解

channel 是 goroutine 之间传递数据的管道,体现了 Go 的座右铭:「不要通过共享内存来通信,要通过通信来共享内存」。make(chan int) 创建无缓冲 channel:发送方 ch <- v 会阻塞,直到有接收方 <-ch 取走,一手交钱一手交货,天然带同步效果;make(chan int, 3) 创建带缓冲的 channel,缓冲区没满时发送不阻塞。

发送方完成后用 close(ch) 关闭 channel,接收方用 for v := range ch 一直收到 channel 关闭为止——「发送方关闭、接收方用 range」是标准分工。接收方也可以用 v, ok := <-ch 的 comma ok 形式判断是否已关闭。对已关闭的 channel 再发送会 panic,从关闭的 channel 接收则立即得到零值加 false。

select 是 channel 的多路复用器:同时等待多个 channel 操作,谁先就绪执行谁,都有就绪就随机挑一个。经典用法是配 time.After 实现超时控制——select { case v := <-ch: ...; case <-time.After(d): 超时 }。掌握 goroutine + channel + select 这三件套,就掌握了 Go 并发编程的骨架。

示例

无缓冲 channel:goroutine 之间的一次握手:

package main

import "fmt"

func square(n int, ch chan int) {
	ch <- n * n // 发送,阻塞直到有人接收
}

func main() {
	ch := make(chan int)
	go square(7, ch)
	result := <-ch // 阻塞直到收到值
	fmt.Println("7 的平方:", result)
}

带缓冲 channel、close 与 range 接收:

package main

import "fmt"

func main() {
	ch := make(chan string, 3)
	ch <- "苹果"
	ch <- "香蕉"
	ch <- "橙子"
	close(ch) // 发送方关闭,range 才能正常结束

	for fruit := range ch {
		fmt.Println("收到:", fruit)
	}
}

select 实现超时控制:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)
	go func() {
		time.Sleep(50 * time.Millisecond)
		ch <- "慢任务的结果"
	}()

	select {
	case msg := <-ch:
		fmt.Println("收到:", msg)
	case <-time.After(200 * time.Millisecond):
		fmt.Println("超时了")
	}
}

常见坑

  • 无缓冲 channel 自我死锁:同一个 goroutine 里先 ch <- v 再 <-ch 必然卡死——发送时还没有接收者。无缓冲 channel 的发送和接收必须分布在不同的 goroutine。
  • 向已关闭的 channel 发送:直接 panic。纪律:只有发送方(且唯一)负责 close;多个发送方时用 WaitGroup 等全部发完再由专人关闭。
  • 关闭不是必须的:channel 不关也能正常收发,GC 会回收。close 的唯一作用是告诉接收方「不会再有新值了」,用于 range 退出或广播完成信号。
  • select 里忘记 default 的语义:加 default 变成非阻塞轮询(没有就绪就走 default),不加则阻塞等待。想要哪种语义要想清楚,轮询写法在循环里还会空转烧 CPU。
  • goroutine 泄漏:goroutine 卡在永远不会有人接收的发送操作上,就永远退不出来,内存和协程数持续上涨。确保每个发送最终都有接收方,或用带超时的 select 兜底。

小结

channel 在 goroutine 间传值,无缓冲同步、有缓冲异步,发送方 close、接收方 range,select 做多路等待与超时。下一节回到日常业务:JSON 编解码。