Select 语句
Select 语句用于同时等待多个 Channel 操作,是 Go 并发编程的重要工具。
Select 基础
基本语法
go
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "来自 ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "来自 ch2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
随机选择
当多个 case 都就绪时,select 会随机选择一个。
go
func main() {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch1 <- 1
ch2 <- 2
select {
case v := <-ch1:
fmt.Println("ch1:", v)
case v := <-ch2:
fmt.Println("ch2:", v)
}
}
Default 分支
非阻塞操作
go
func main() {
ch := make(chan int)
select {
case value := <-ch:
fmt.Println("收到:", value)
default:
fmt.Println("没有数据")
}
}
发送不阻塞
go
func main() {
ch := make(chan int, 1)
select {
case ch <- 1:
fmt.Println("发送成功")
default:
fmt.Println("发送失败")
}
}
超时处理
time.After
go
func main() {
ch := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch <- "结果"
}()
select {
case result := <-ch:
fmt.Println(result)
case <-time.After(1 * time.Second):
fmt.Println("超时")
}
}
time.NewTimer
go
func main() {
ch := make(chan string)
timer := time.NewTimer(1 * time.Second)
go func() {
time.Sleep(2 * time.Second)
ch <- "结果"
}()
select {
case result := <-ch:
fmt.Println(result)
case <-timer.C:
fmt.Println("超时")
}
}
可重置超时
go
func main() {
ch := make(chan string)
timer := time.NewTimer(1 * time.Second)
go func() {
for i := 0; i < 3; i++ {
time.Sleep(500 * time.Millisecond)
ch <- fmt.Sprintf("消息 %d", i)
}
}()
for {
select {
case msg := <-ch:
fmt.Println(msg)
timer.Reset(1 * time.Second)
case <-timer.C:
fmt.Println("超时")
return
}
}
}
多 Channel 监听
监听多个 Channel
go
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
done := make(chan bool)
go func() {
for i := 0; i < 3; i++ {
ch1 <- fmt.Sprintf("ch1-%d", i)
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
for i := 0; i < 3; i++ {
ch2 <- fmt.Sprintf("ch2-%d", i)
time.Sleep(150 * time.Millisecond)
}
}()
go func() {
time.Sleep(1 * time.Second)
done <- true
}()
for {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
case <-done:
fmt.Println("完成")
return
}
}
}
优先级处理
go
func main() {
high := make(chan string)
low := make(chan string)
go func() {
for i := 0; i < 5; i++ {
low <- fmt.Sprintf("低优先级 %d", i)
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
time.Sleep(250 * time.Millisecond)
high <- "高优先级"
}()
for {
select {
case msg := <-high:
fmt.Println(msg)
return
case msg := <-low:
fmt.Println(msg)
}
}
}
空 Select
go
func blockForever() {
select {}
}
func main() {
go blockForever()
time.Sleep(time.Second)
fmt.Println("仍在运行")
}
Select 模式
模式 1:超时控制
go
func withTimeout(ch <-chan int, timeout time.Duration) (int, error) {
select {
case value := <-ch:
return value, nil
case <-time.After(timeout):
return 0, fmt.Errorf("超时")
}
}
模式 2:心跳检测
go
func heartbeat(interval time.Duration) <-chan time.Time {
ch := make(chan time.Time)
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for now := range ticker.C {
select {
case ch <- now:
default:
}
}
}()
return ch
}
func main() {
heartbeatCh := heartbeat(500 * time.Millisecond)
for i := 0; i < 5; i++ {
select {
case now := <-heartbeatCh:
fmt.Println("心跳:", now.Format("15:04:05"))
}
}
}
模式 3:取消操作
go
func worker(ch <-chan int, done <-chan struct{}) {
for {
select {
case value := <-ch:
fmt.Println("处理:", value)
case <-done:
fmt.Println("收到取消信号")
return
}
}
}
func main() {
ch := make(chan int)
done := make(chan struct{})
go worker(ch, done)
go func() {
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(100 * time.Millisecond)
}
close(done)
}()
time.Sleep(2 * time.Second)
}
Select 最佳实践
1. 总是包含 default 或超时
go
// 推荐
select {
case value := <-ch:
return value
case <-time.After(timeout):
return defaultValue
}
// 不推荐(可能永久阻塞)
select {
case value := <-ch:
return value
}
2. 避免在循环中创建 Timer
go
// 推荐
timer := time.NewTimer(timeout)
defer timer.Stop()
for {
select {
case value := <-ch:
return value
case <-timer.C:
return defaultValue
}
}
// 不推荐
for {
select {
case value := <-ch:
return value
case <-time.After(timeout): // 每次创建新 Timer
return defaultValue
}
}
3. 使用空 Select 阻塞
go
func block() {
select {} // 永久阻塞
}
实战示例
示例 1:多路复用
go
func multiplex(channels ...<-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
cases := make([]reflect.SelectCase, len(channels))
for i, ch := range channels {
cases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ch),
}
}
for len(cases) > 0 {
chosen, value, ok := reflect.Select(cases)
if !ok {
cases = append(cases[:chosen], cases[chosen+1:]...)
continue
}
out <- int(value.Int())
}
}()
return out
}
示例 2:速率限制
go
func rateLimit(ch <-chan int, rate time.Duration) <-chan int {
out := make(chan int)
ticker := time.NewTicker(rate)
defer ticker.Stop()
go func() {
defer close(out)
for value := range ch {
<-ticker.C
out <- value
}
}()
return out
}
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()
limited := rateLimit(ch, 500*time.Millisecond)
for value := range limited {
fmt.Println(value)
}
}
练习
练习 1:竞速条件
实现两个 goroutine 竞速,先完成的返回结果。
答案
go
package main
import (
"fmt"
"time"
)
func race(id int, ch chan<- int) {
time.Sleep(time.Duration(id) * 100 * time.Millisecond)
ch <- id
}
func main() {
ch := make(chan int)
for i := 1; i <= 5; i++ {
go race(i, ch)
}
winner := <-ch
fmt.Printf("获胜者: %d\n", winner)
}
总结
- Select 等待多个 Channel 操作
- 随机选择就绪的 case
- Default 实现非阻塞操作
- time.After 实现超时
- 支持多 Channel 监听
- 避免在循环中创建 Timer
下一章:并发模式