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")
	}
}
相关推荐
BTU_YC4 小时前
Neo4j查询计划完全指南:读懂数据库的“执行蓝图“
数据库·neo4j
非极限码农4 小时前
Neo4j图数据库上手指南
大数据·数据库·数据分析·neo4j
mit6.8244 小时前
[C# starter-kit] 命令/查询职责分离CQRS | MediatR |
java·数据库·c#
苏打水com5 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
莫叫石榴姐5 小时前
SQL百题斩:从入门到精通,一站式解锁数据世界
大数据·数据仓库·sql·面试·职场和发展
shan~~5 小时前
linux达梦数据库操作
linux·数据库·chrome
武文斌776 小时前
项目学习总结:LVGL图形参数动态变化、开发板的GDB调试、sqlite3移植、MQTT协议、心跳包
linux·开发语言·网络·arm开发·数据库·嵌入式硬件·学习
CoderIsArt6 小时前
SQLite架构
数据库·sqlite
lixora7 小时前
银河麒麟高级服务器操作系统(ADM64 版)V10(SP1)搭建 Oracle 19c RAC
数据库
郝学胜-神的一滴7 小时前
使用Linux的read和write系统函数操作文件
linux·服务器·开发语言·数据库·c++·程序人生·软件工程