GO_并发编程---select

一、select 基础介绍

1. select 与 switch 区别

  • switch:匹配相等条件,可写任意表达式;
  • select每个 case 只能是 channel IO 操作(读通道 / 写通道),专门用于同时监听多个通道读写状态。

2. select 标准语法结构

go

Go 复制代码
select {
case <-chan1:
    // 通道chan1可读,执行该分支
case chan2 <- 100:
    // 通道chan2可写,执行该分支
default:
    // 所有case都阻塞无就绪IO时,立刻执行default(非阻塞)
}

3. 核心执行规则

  1. 扫描所有 case,收集当前就绪的通道 IO 分支;
  2. 多个 case 同时就绪:随机选择其中一个执行,无固定顺序;
  3. 若无任何 case 就绪,且存在default:直接走 default,不阻塞;
  4. 若无任何 case 就绪、也无 default:select 永久阻塞,直到某个通道 IO 就绪。

二、实战案例 1:select 实现斐波那契数列(多通道监听:生产 + 退出信号)

需求:生产者持续向通道写入斐波那契数字,消费者读取 8 个数字后发送退出信号,生产者收到信号结束协程。

go

Go 复制代码
package main

import "fmt"

// 生产者:ch只写,quit只读
func fibonacci(ch chan<- int, quit <-chan bool) {
	x, y := 1, 1
	for {
		select {
		case ch <- x:
			// 成功写入数字,更新斐波那契
			x, y = y, x+y
		case flag := <-quit:
			// 收到退出信号,结束生产者协程
			fmt.Println("flag =", flag)
			return
		}
	}
}

func main() {
	ch := make(chan int)    // 传输斐波那契数字
	quit := make(chan bool) // 退出信号通道

	// 消费者协程:读取8个数字后发送退出信号
	go func() {
		for i := 0; i < 8; i++ {
			num := <-ch
			fmt.Println(num)
		}
		// 读取完成,通知生产者退出
		quit <- true
	}()

	// 启动生产者
	fibonacci(ch, quit)
	fmt.Println("程序结束")
}

流程梳理

  1. 主协程启动消费者协程;
  2. fibonacci通过 select 同时监听「写数字通道」和「退出通道」;
  3. 前 8 次:ch 可写,持续输出 1,1,2,3,5,8,13,21;
  4. 读完 8 个数字后,消费者向 quit 发送 true;
  5. select 捕获 quit 通道数据,生产者 return 退出函数。

三、实战案例 2:select 配合 time.After 实现超时控制

业务场景:监听通道数据,若 3 秒内没有数据到达,判定为超时,主动退出程序。

go

Go 复制代码
package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	quit := make(chan bool)

	// 消费协程:监听通道+超时
	go func() {
		for {
			select {
			case num := <-ch:
				fmt.Println("num =", num)
			case <-time.After(3 * time.Second):
				fmt.Println("超时,无数据输入")
				quit <- true
				return
			}
		}
	}()

	// 主线程持续向通道发送5个数字,间隔1秒
	for i := 0; i < 5; i++ {
		ch <- i
		time.Sleep(time.Second)
	}

	// 等待超时信号
	<-quit
	fmt.Println("程序结束")
}

运行逻辑

  • 主线程每隔 1s 发送数字,3 秒超时永远不会触发;
  • 如果注释掉主线程发送逻辑,3 秒后time.After通道就绪,进入超时分支,程序退出。

四、select 关键知识点总结

  1. 适用场景:同时监听多个 channel的读写状态,是协程多路复用核心;
  2. case 限制:只能是通道读 / 写操作,不能是普通表达式;
  3. 多分支同时就绪:随机任选一个执行;
  4. default 作用:让 select 变成非阻塞,无就绪通道时直接执行 default;
  5. 超时经典用法:case <-time.After(时长),无需手动创建 Timer;
  6. 多信号协同:可同时监听业务数据通道 + 退出信号通道,优雅关闭协程。
相关推荐
不正经学生1 小时前
C语言预处理详解:编译器真正动手之前的那些事
c语言·开发语言·算法·面试·bug
门思科技1 小时前
LoRaWAN 设备类别:Class A、B、C 对比与选型指南
c语言·开发语言·php
恋恋西风4 小时前
C++ 理解 std::thread 在单核和多核上的行为差异
开发语言·c++
学长毕业设计4 小时前
基于SpringBoot的公益基金管理系统(源码+文档+讲解视频)
java·spring boot·后端
Brilliantwxx4 小时前
【Linux】 进程(9)程序与进程地址空间(基础+进阶+面试题)
linux·运维·服务器·开发语言·c++
东小西4 小时前
【SAA实战】第 3 篇 · 工具调用全攻略:把业务能力交给 Agent 自己调度
java·后端·spring
东小西4 小时前
【SAA实战】第 4 篇 · Agent 短期记忆:saver 让 Agent 跨轮记得住(threadId 隔离)
java·后端·spring
BizzZ_5 小时前
C++(22)——类型转换和IO流
开发语言·c++
阿kun要赚马内5 小时前
MySQL 索引基础
后端·mysql
小范同学_5 小时前
JDK1.7 与 JDK1.8 HashMap 底层原理对比 + 数组并发扩容死循环详解
java·开发语言