Select语句 #
一、Select概述 #
select语句用于处理多个channel操作,类似switch,但专用于channel。
二、基本语法 #
2.1 结构 #
go
select {
case v := <-ch1:
// 从ch1接收
case ch2 <- v:
// 向ch2发送
default:
// 默认操作
}
2.2 示例 #
go
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(time.Second)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(500 * time.Millisecond)
ch2 <- "from ch2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
三、随机选择 #
3.1 随机特性 #
当多个case同时就绪时,随机选择一个:
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)
}
}
3.2 公平处理 #
go
func fairSelect(ch1, ch2 <-chan int) {
for {
select {
case v := <-ch1:
process(v)
case v := <-ch2:
process(v)
}
}
}
四、超时处理 #
4.1 time.After #
go
select {
case v := <-ch:
fmt.Println(v)
case <-time.After(time.Second):
fmt.Println("Timeout")
}
4.2 time.NewTimer #
go
timer := time.NewTimer(time.Second)
defer timer.Stop()
select {
case v := <-ch:
fmt.Println(v)
case <-timer.C:
fmt.Println("Timeout")
}
4.3 带超时的操作 #
go
func fetchWithTimeout(url string, timeout time.Duration) ([]byte, error) {
ch := make(chan []byte)
go func() {
resp, _ := http.Get(url)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
ch <- data
}()
select {
case data := <-ch:
return data, nil
case <-time.After(timeout):
return nil, errors.New("timeout")
}
}
五、非阻塞操作 #
5.1 default分支 #
go
select {
case v := <-ch:
fmt.Println(v)
default:
fmt.Println("No data")
}
5.2 非阻塞发送 #
go
select {
case ch <- v:
fmt.Println("Sent")
default:
fmt.Println("Channel full")
}
5.3 非阻塞接收 #
go
select {
case v := <-ch:
fmt.Println(v)
default:
fmt.Println("No data available")
}
六、循环select #
6.1 for-select模式 #
go
func main() {
ch := make(chan int)
done := make(chan struct{})
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(done)
}()
for {
select {
case v := <-ch:
fmt.Println(v)
case <-done:
return
}
}
}
6.2 带退出条件 #
go
func worker(ch <-chan int, cancel <-chan struct{}) {
for {
select {
case v := <-ch:
fmt.Println(v)
case <-cancel:
return
}
}
}
七、实际应用 #
7.1 多数据源合并 #
go
func merge(ch1, ch2 <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for {
select {
case v, ok := <-ch1:
if !ok {
ch1 = nil
} else {
out <- v
}
case v, ok := <-ch2:
if !ok {
ch2 = nil
} else {
out <- v
}
}
if ch1 == nil && ch2 == nil {
return
}
}
}()
return out
}
7.2 超时重试 #
go
func retryWithTimeout(fn func() error, maxAttempts int, timeout time.Duration) error {
for i := 0; i < maxAttempts; i++ {
ch := make(chan error, 1)
go func() {
ch <- fn()
}()
select {
case err := <-ch:
if err == nil {
return nil
}
case <-time.After(timeout):
continue
}
}
return errors.New("max attempts reached")
}
7.3 心跳检测 #
go
func heartbeat(interval time.Duration, done <-chan struct{}) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
fmt.Println("Heartbeat")
case <-done:
return
}
}
}
7.4 限流 #
go
func rateLimiter(requests <-chan Request, limit int) {
ticker := time.NewTicker(time.Second / time.Duration(limit))
defer ticker.Stop()
for req := range requests {
<-ticker.C
go process(req)
}
}
八、常见模式 #
8.1 优雅退出 #
go
func server(shutdown <-chan struct{}) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// 处理任务
case <-shutdown:
// 清理资源
return
}
}
}
8.2 多任务等待 #
go
func waitAll(tasks []Task) {
done := make(chan struct{}, len(tasks))
for _, task := range tasks {
go func(t Task) {
t.Run()
done <- struct{}{}
}(task)
}
for i := 0; i < len(tasks); i++ {
<-done
}
}
8.3 取消操作 #
go
func withCancel(ctx context.Context, fn func() error) error {
ch := make(chan error, 1)
go func() {
ch <- fn()
}()
select {
case err := <-ch:
return err
case <-ctx.Done():
return ctx.Err()
}
}
九、常见错误 #
9.1 忘记default #
go
select {
case v := <-ch:
fmt.Println(v)
// 忘记default会阻塞
}
9.2 goroutine泄漏 #
go
func leak() {
ch := make(chan int)
go func() {
select {
case v := <-ch:
fmt.Println(v)
}
}()
// 如果ch永不发送,goroutine泄漏
}
9.3 饥饿问题 #
go
for {
select {
case v := <-ch1:
// 如果ch1持续有数据,ch2可能饥饿
case v := <-ch2:
}
}
十、最佳实践 #
10.1 使用context #
go
select {
case v := <-ch:
fmt.Println(v)
case <-ctx.Done():
return ctx.Err()
}
10.2 避免空select #
go
select {} // 永久阻塞
10.3 处理nil channel #
go
var ch1 chan int // nil
select {
case v := <-ch1: // 永不选中
fmt.Println(v)
case v := <-ch2:
fmt.Println(v)
}
十一、总结 #
Select要点:
| 特性 | 说明 |
|---|---|
| 随机选择 | 多个就绪时随机选一个 |
| 阻塞 | 无就绪case时阻塞 |
| default | 无就绪case时执行default |
| 超时 | 配合time.After使用 |
关键点:
- 多路复用:同时监听多个channel
- 随机选择:多个就绪时随机选择
- 超时处理:配合time.After实现超时
- 非阻塞:使用default实现非阻塞
- 循环模式:for-select处理持续事件
准备好学习Channel模式了吗?让我们进入下一章!
最后更新:2026-03-26