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")
	}
}
相关推荐
易云码4 分钟前
信息安全建设方案,网络安全等保测评方案,等保技术解决方案,等保总体实施方案(Word原件)
数据库·物联网·安全·web安全·低代码
newxtc9 分钟前
【客观理性深入讨论国产中间件及数据库-科创基础软件】
数据库·中间件·国产数据库·国产中间件·科创
水月梦镜花11 分钟前
redis:list列表命令和内部编码
数据库·redis·list
有梦想的咸鱼_1 小时前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
MonkeyKing_sunyuhua1 小时前
ubuntu22.04 docker-compose安装postgresql数据库
数据库·docker·postgresql
天郁青1 小时前
数据库交互的本地项目:后台管理系统
数据库·交互
马剑威(威哥爱编程)1 小时前
MongoDB面试专题33道解析
数据库·mongodb·面试
小光学长2 小时前
基于vue框架的的流浪宠物救助系统25128(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库·vue.js·宠物
零炻大礼包2 小时前
【SQL server】数据库远程连接配置
数据库
zmgst2 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql