Go Channel同步与多路复用详解
Channel是goroutine间通信和同步的主要机制。
Channel同步机制
无缓冲channel同步
Go
// 无缓冲channel:发送等待接收
ch := make(chan int)
go func() {
ch <- 1 // 阻塞等待接收
}()
<-ch // 阻塞等待发送
// 双方同时就绪才能继续
有缓冲channel异步
Go
// 有缓冲channel:缓冲满才阻塞
ch := make(chan int, 3)
ch <- 1 // 不阻塞(缓冲未满)
ch <- 2
ch <- 3
ch <- 4 // 阻塞(缓冲满)
v := <-ch // 取出1,ch <- 4继续
| 类型 | 发送行为 | 接收行为 | 用途 |
|---|---|---|---|
| 无缓冲 | 等待接收 | 等待发送 | 同步信号 |
| 有缓冲 | 缓冲满阻塞 | 缓冲空阻塞 | 数据传递 |
Select多路复用
基本用法
Go
ch1 := make(chan int)
ch2 := make(chan int)
select {
case v := <-ch1:
fmt.Println("ch1:", v)
case v := <-ch2:
fmt.Println("ch2:", v)
default:
fmt.Println("无数据")
}
非阻塞操作
Go
select {
case ch <- data:
// 发送成功
default:
// 发送失败(缓冲满)
}
循环select
Go
for {
select {
case v := <-ch1:
process1(v)
case v := <-ch2:
process2(v)
case <-quit:
return // 退出循环
}
}
超时控制
time.After
Go
select {
case v := <-ch:
fmt.Println("收到:", v)
case <-time.After(5 * time.Second):
fmt.Println("超时")
}
time.NewTimer可重用
Go
timer := time.NewTimer(5 * time.Second)
for {
select {
case v := <-ch:
fmt.Println("收到:", v)
timer.Reset(5 * time.Second) // 重置
case <-timer.C:
fmt.Println("超时")
return
}
}
Channel关闭处理
关闭channel
Go
ch := make(chan int, 5)
// 发送者关闭
ch <- 1
ch <- 2
close(ch)
// 接收者检测关闭
for v := range ch {
fmt.Println(v) // 1, 2
}
// 自动退出,不会阻塞
判断channel关闭
Go
v, ok := <-ch
if ok {
fmt.Println("收到:", v)
} else {
fmt.Println("channel已关闭")
}
// 从已关闭channel接收返回零值+false
双向关闭检测
Go
select {
case v, ok := <-ch:
if !ok {
fmt.Println("ch已关闭")
return
}
fmt.Println(v)
}
只有发送者关闭channel,接收者不要关闭。
多路复用模式
优先级选择
Go
// select无优先级,随机选择就绪case
// 模拟优先级:
for {
select {
case v := <-priority:
// 优先处理
default:
select {
case v := <-normal:
// 正常处理
case <-priority:
// 检查优先channel
}
}
}
扇入模式
Go
func fanIn(ch1, ch2 <-chan int) <-chan int {
out := make(chan int)
go func() {
for {
select {
case v := <-ch1:
out <- v
case v := <-ch2:
out <- v
}
}
}()
return out
}
扇出模式
Go
func fanOut(ch <-chan int) []chan int {
outputs := make([]chan int, 3)
for i := 0; i < 3; i++ {
outputs[i] = make(chan int)
go func(out chan<- int) {
for v := range ch {
out <- v
}
}(outputs[i])
}
return outputs
}
Select陷阱
阻塞在nil channel
Go
var ch chan int // nil
select {
case v := <-ch: // 永久阻塞
// 不会执行
}
nil channel在select中永久阻塞,可用于禁用case。
关闭后的发送
Go
ch := make(chan int)
close(ch)
ch <- 1 // panic: send on closed channel
多次关闭
Go
close(ch)
close(ch) // panic: close of closed channel
同步场景对比
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 单次同步 | 无缓冲channel | 等待完成 |
| 数据传递 | 有缓冲channel | 异步传递 |
| 多通道选择 | select | 多路复用 |
| 超时控制 | select + time.After | 避免阻塞 |
| 退出信号 | close channel | 广播退出 |
| 动态禁用 | nil channel | select不执行 |
要点总结
- 无缓冲channel同步强,有缓冲异步传递
- select多路复用多个channel
- default实现非阻塞操作
- time.After实现超时控制
- 只有发送者关闭channel
- range遍历自动检测关闭
- nil channel在select中阻塞(可禁用case)
- close后发送panic,多次close panic
- close向所有接收者广播退出信号
📝 发现内容有误?点击此处直接编辑