GoLang - select

文章目录

Go 语言 select 语句

1、概述

  • select 是 Go 中的一个控制结构,类似于 switch 语句;
  • select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收;
  • select 语句会监听所有指定的通道上的操作:
    • 一旦其中一个通道准备好就会执行相应的代码块;
    • 如果多个通道都准备好,那么 select 语句会随机选择一个通道执行;
    • 如果所有通道都没有准备好,那么执行 default 块中的代码;
    • 如果所有通道都没有准备好,并且没有 default 块,那么 select 语句将会阻塞,直到至少一个通道准备好为止;(这意味着程序会一直等待,直到有一个通道可以进行操作)

2、语法

go 复制代码
select {
  case <- channel1:
    // 执行的代码
  case value := <- channel2:
    // 执行的代码
  case channel3 <- value:
    // 执行的代码

    // 你可以定义任意数量的 case

  default:
    // 所有通道都没有准备好,执行的代码
}
  • 每个 case 都必须是一个通道;
  • 所有 channel 表达式都会被求值;
  • 所有被发送的表达式都会被求值;
  • 如果任意摸个通道可以运行,它就执行,其他被忽略;
  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行;
    • 否则,如果有 default 子句,则执行该语句;
    • 如果没有 default 子句,select 将阻塞,直到某个通道可以运行;
    • Go不会重新对 channel 或值进行求值;

3、简单实例

  • 没有 default 的 select;
go 复制代码
func SimpleDemoNoDefault() {
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		time.Sleep(1 * time.Second)
		c1 <- "channel one"
	}()

	go func() {
		time.Sleep(1 * time.Second)
		c2 <- "channel two"
	}()

	for i := 0; i < 3; i++ {
		select {
		case msg1 := <-c1:
			fmt.Println("received", msg1)
		case msg2 := <-c2:
			fmt.Println("received", msg2)
		}
	}
}
  • 有 default 的 select:
go 复制代码
func SimpleDemoWithDefault() {
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		for {
			c1 <- "from 1"
		}
	}()

	go func() {
		for {
			c1 <- "from 2"
		}
	}()

	for {
		time.Sleep(1 * time.Second)
		select {
		case msg1 := <-c1:
			fmt.Println(msg1)
		case msg2 := <-c2:
			fmt.Println(msg2)
		default:
			fmt.Println("no message received")
		}
	}
}

4、实现原理

通过 select 语句,可以实现主线程和其他线程之间的互动;

实现原理

详情参考:https://www.jb51.net/article/259610.htm

  • GoLang实现select时,定义了一个数据结构表示每个case语句(包含defaultdefault实际上是一种特殊的case);
  • select执行过程可以看成一个函数,函数输入case数组,输出选中的case,然后程序流程转到选中的case块;

执行流程

创建select 注册case 执行select 释放select

  • 在默认情况下,select 在编译阶段经过如下过程的处理:
    • 将所有 case 转换成包含 channel 以及类型等信息的 scase 结构体;
    • 编译器调用运行时函数 selectgo 来执行 select 语句,这个函数会根据 scase 结构体数组中的各个 case 条件,选择其中一个 case 来执行,并返回被选择 scase 结构体的索引;
    • 如果当前 scase 是一个接收操作,函数会返回一个布尔值,表示接收操作是否成功;
    • 编译器会根据 scase 结构体数组中的各个 case 条件,生成一组 if 语句,每个 if 语句会判断当前的 case 是否是被选择的 case,如果是,则执行相应的操作;

scase 数据结构

源码包 src/runtime/select.go

go 复制代码
type scase struct {
	c    *hchan         // chan
	elem unsafe.Pointer // data element
}
  • c:表示通道,存储 case 所关联的通道,不包含 default;这也说明了一个case 语句只能操作一个 channel;
  • elem:表示数据类型,用于存储 case 所关联的数据元素;

判断某个 scase 属于什么操作

go 复制代码
select {
case x := <-ch:
    fmt.Println("接收到数据:", x)
case ch <- 10:
    fmt.Println("发送数据成功")
default:
    fmt.Println("没有进行任何操作")
}
  • <-ch:表示接收操作,将通道 ch 中的数据赋值给变量 x;
  • ch <- 10:表示发送操作,将数据 10 发送到通道 ch 中;
  • default:表示默认操作,当没有其他 case 可执行时,执行该操作;

5、应用场景

多通道读取

go 复制代码
func MultiChannelRead() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		ch1 <- 1
	}()

	go func() {
		ch2 <- 2
	}()

	select {
	case data := <-ch1:
		fmt.Println("received data from ch1: ", data)
	case data := <-ch2:
		fmt.Println("receiver data from ch2: ", data)
	}
}

多通道写入

go 复制代码
func MultiChannelWrite() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		ch1 <- 1
	}()

	go func() {
		ch2 <- 2
	}()

	select {
	case <-ch1:
		fmt.Println("Send data to ch1")
	case <-ch2:
		fmt.Println("Send data to ch2")
	}
}

超时控制

go 复制代码
func TimeoutControl() {
	ch := make(chan int)

	go func() {
		time.Sleep(2 * time.Second)
	}()

	select {
	case data := <-ch:
		fmt.Println("Received data: ", data)
	case <-time.After(1 * time.Second):
		fmt.Println("Timeout occurred")
	}
}
相关推荐
阿里云大数据AI技术40 分钟前
云栖实录|MaxCompute全新升级:AI时代的原生数据仓库
大数据·数据库·云原生
不剪发的Tony老师1 小时前
Valentina Studio:一款跨平台的数据库管理工具
数据库·sql
重生之我要当java大帝1 小时前
java微服务-尚医通-编写医院设置接口下
java·开发语言·sql
weixin_307779131 小时前
在 Microsoft Azure 上部署 ClickHouse 数据仓库:托管服务与自行部署的全面指南
开发语言·数据库·数据仓库·云计算·azure
六元七角八分2 小时前
pom.xml
xml·数据库
Achou.Wang2 小时前
源码分析 golang bigcache 高性能无 GC 开销的缓存设计实现
开发语言·缓存·golang
虚行2 小时前
Mysql 数据同步中间件 对比
数据库·mysql·中间件
奥尔特星云大使2 小时前
mysql读写分离中间件Atlas安装部署及使用
数据库·mysql·中间件·读写分离·atlas
牛马baby2 小时前
【mysql】in 用到索引了吗?
数据库·mysql·in
杀气丶2 小时前
L2JBR - 修复数据库编码为UTF8
数据库·sql·oracle