在 Go 语言中,select 语句用于处理多个通道的操作,它可以同时等待多个通道的读写事件,并在其中一个通道就绪时执行对应的分支。select 的用法类似于 switch,但专门用于通道操作,是并发编程中非常重要的工具。
一、基本语法
select {
case <-ch1:
// 当 ch1 有数据可读时执行
case ch2 <- value:
// 当 ch2 可写入数据时执行
case <-time.After(time.Second):
// 超时处理(可选)
default:
// 当所有通道都未就绪时执行(可选)
}
select会同时监听所有case中的通道操作。- 当多个
case同时就绪时,随机选择一个执行(避免饥饿问题)。 - 如果所有
case都未就绪:- 若有
default分支,则立即执行default。 - 若无
default分支,则select会阻塞,直到某个case就绪。
- 若有
二、常见用法
1. 等待多个通道,处理先就绪的
同时监听多个通道,哪个先有数据(或可写入)就处理哪个。
package main
import "fmt"
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() { ch1 <- 1 }()
go func() { ch2 <- 2 }()
select {
case v := <-ch1:
fmt.Println("ch1 就绪:", v)
case v := <-ch2:
fmt.Println("ch2 就绪:", v)
}
}
// 输出可能是 "ch1 就绪: 1" 或 "ch2 就绪: 2"(随机)
2. 带超时的通道操作
使用 time.After 实现超时控制,避免永久阻塞。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
select {
case v := <-ch:
fmt.Println("收到数据:", v)
case <-time.After(1 * time.Second): // 1秒后超时
fmt.Println("超时")
}
}
// 输出:超时(因为 ch 始终没有数据)
3. 非阻塞的通道操作
通过 default 实现非阻塞读写,避免阻塞当前 goroutine。
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 10 // 通道已存数据
// 非阻塞读
select {
case v := <-ch:
fmt.Println("读取到:", v)
default:
fmt.Println("无法读取(通道为空)")
}
// 非阻塞写
select {
case ch <- 20:
fmt.Println("写入成功")
default:
fmt.Println("无法写入(通道已满)")
}
}
// 输出:
// 读取到: 10
// 无法写入(通道已满)
4. 关闭通道的处理
如果通道被关闭,读取操作会立即返回零值,可通过多返回值判断通道是否关闭。
package main
import "fmt"
func main() {
ch := make(chan int)
close(ch) // 关闭通道
select {
case v, ok := <-ch:
if ok {
fmt.Println("收到数据:", v)
} else {
fmt.Println("通道已关闭")
}
}
}
// 输出:通道已关闭
5. 无限等待多个通道
结合 for 循环,持续监听多个通道事件。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
for {
time.Sleep(1 * time.Second)
ch1 <- "来自 ch1 的消息"
}
}()
go func() {
for {
time.Sleep(2 * time.Second)
ch2 <- "来自 ch2 的消息"
}
}()
// 持续监听两个通道
for {
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
}
}
}
// 输出(间隔性):
// 来自 ch1 的消息
// 来自 ch1 的消息
// 来自 ch2 的消息
// ...
三、注意事项
select中的case必须是通道操作(读或写),不能是普通表达式。- 若
select中没有任何case(空select),会导致当前 goroutine 永久阻塞(deadlock)。 - 当通道被关闭后,读取操作会立即触发(返回零值),需通过
ok判断是否有效数据。 time.After会创建一个新通道,超时后发送当前时间,需注意其生命周期(避免内存泄漏)。
select 是 Go 并发模型中协调多个通道的核心机制,灵活使用可实现高效的事件驱动逻辑。