理解 Go 语言中的 select 用法

简介: 本文深入解析了Go语言中`select`的用法,它类似于`switch case`,但仅用于通道(channel)的操作。文章通过多个示例说明了`select`的基本用法、避免死锁的方法、随机性特点以及如何实现超时机制。同时总结了`select`与`switch`的区别:`select`专用于通道操作,case执行是随机的,需注意死锁问题,且不支持`fallthrough`和函数表达式。

今天深入了解下Go语言中select的用法,和switch case很类似,用法比较单一,它仅能用于 信道/通道 的相关操作,每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句

go

代码解读

复制代码

//语法如下:
select{
    case <-ch1:
        ...
    case data := <-ch2:
        ...
    case ch3<-data:
        ...
    default:
        默认操作
}

接下来,我们来看几个例子帮助理解这个 select 的模型。

1、最简单的例子

go

代码解读

复制代码

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

    c2 <- "hello"

    select {
    case msg1 := <-c1:
      fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
      fmt.Println("c2 received: ", msg2)
    default:
      fmt.Println("No data received.")
    }
}

在运行 select 时,会遍历所有)的 case 表达式,只要有一个信道有接收到数据,那么 select 就结束,所以输出如下c2 received:  hello,c2接收到数据了

2. 避免造成死锁

select 在执行过程中,必须命中其中的某一分支。如果在遍历完所有的 case 后,若没有命中任何一个 case 表达式,就会进入 default 里的代码分支。

但刚好这时你没有写 default 分支,会发生什么呢?这时select 就会阻塞,直到有某个 case 可以命中,而如果一直没有命中,select 就会抛出 deadlock 的错误,就像下面这样子。

go

代码解读

复制代码

package main
import (
    "fmt"
)
func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
    // c2 <- "hello"
    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
        // default:
        //     fmt.Println("No data received.")
    }
}

运行后输出如下

go

代码解读

复制代码

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select]:
nginxlog/tools.OutPrintf()
	D:/work/perlearn/go_space/total/tools/parasenginx.go:233 +0xc5
main.main()
	D:/work/perlearn/go_space/total/main.go:8 +0x17
Process finished with exit code 2

3、解决这个问题的方法有两种

一个是,养成好习惯,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。这时再执行上面代码就不会deadlock

go

代码解读

复制代码

package main

import (
    "fmt"
)
func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
  // c2 <- "hello"
    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
    default:
    }
}

另一个是,确保其中某一个信道可以接收到数据

go

代码解读

复制代码

package main

import (
    "fmt"
    "time"
)
func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
  // 开启一个协程,可以发送数据到信道
    go func() {
        time.Sleep(time.Second * 1)
        c2 <- "hello"
    }()
    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
    }
}

4、select 随机性

之前学过 switch 的时候,知道了 switch 里的 case 是顺序执行的,但在 select 里却不是。

go

代码解读

复制代码

func main(){
    c1 := make(chan string, 1)
	c2 := make(chan string, 1)
	c1 <- "hello"
	c2 <- "123123"
	select {
	case msg1 := <-c1:
		fmt.Println("c1 received: ", msg1)
	case msg2 := <-c2:
		fmt.Println("c2 received: ", msg2)
	}
}

5. select 的超时

当 case 里的信道始终没有接收到数据时,而且也没有 default 语句时,select 整体就会阻塞,但是有时我们并不希望 select 一直阻塞下去,这时候就可以手动设置一个超时时间

go

代码解读

复制代码

func main(){
	ch1:=make(chan string,1)
	ch2:=make(chan string,1)
	timeout:=make(chan bool,1)
	go func(ch chan bool, t int) {
		time.Sleep(time.Second * time.Duration(t))
		ch <- true
	}(timeout,2)
	select {
	case msg:=<-ch1:
		fmt.Println("ch1 recvied data",msg)
	case ms2:=<-ch2:
		fmt.Println("ch2 recvied data",ms2)
	case <-timeout:
		fmt.Println("Timeout, exit.")
	}
}

输出如下

text

代码解读

复制代码

Timeout, exit.

6、 总结一下

select 与 switch 原理很相似,但它的使用场景更特殊,学习了本篇文章,你需要知道如下几点区别:

  • select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;
  • select 的 case 是随机的,而 switch 里的 case 是顺序执行;
  • select 要注意避免出现死锁,同时也可以自行实现超时机制;
  • select 里没有类似 switch 里的 fallthrough 的用法;
  • select 不能像 switch 一样接函数或其他表达式。


转载来源:https://juejin.cn/post/7047778400404144159


相关文章
|
2月前
|
存储 SQL 关系型数据库
MySQL 中的回表是什么?
在 MySQL 中,“回表”是指通过二级索引查询时,因二级索引仅存储索引字段值和主键值,需再根据主键到聚簇索引查找完整行数据的过程。此操作涉及两次索引查找,可能增加 IO 消耗,影响性能。优化方法包括使用覆盖索引或联合索引,避免回表,提升查询效率。合理设计索引对高并发、大数据量场景下的数据库性能至关重要。
166 17
|
6月前
|
前端开发 JavaScript Java
java常用数据判空、比较和类型转换
本文介绍了Java开发中常见的数据处理技巧,包括数据判空、数据比较和类型转换。详细讲解了字符串、Integer、对象、List、Map、Set及数组的判空方法,推荐使用工具类如StringUtils、Objects等。同时,讨论了基本数据类型与引用数据类型的比较方法,以及自动类型转换和强制类型转换的规则。最后,提供了数值类型与字符串互相转换的具体示例。
262 3
|
5月前
|
测试技术 数据库 Python
Python装饰器实战:打造高效性能计时工具
在数据分析中,处理大规模数据时,分析代码性能至关重要。本文介绍如何使用Python装饰器实现性能计时工具,在不改变现有代码的基础上,方便快速地测试函数执行时间。该方法具有侵入性小、复用性强、灵活度高等优点,有助于快速发现性能瓶颈并优化代码。通过设置循环次数参数,可以更准确地评估函数的平均执行时间,提升开发效率。
167 61
Python装饰器实战:打造高效性能计时工具
|
2月前
|
负载均衡 前端开发 Java
SpringCloud调用组件Feign
本文深入探讨微服务Spring体系中的Feign组件。Feign是一个声明式Web服务客户端,支持注解、编码器/解码器,与Spring MVC注解兼容,并集成Eureka、负载均衡等功能。文章详细介绍了SpringCloud整合Feign的步骤,包括依赖引入、客户端启用、接口创建及调用示例。同时,还涵盖了Feign的核心配置,如超时设置、拦截器实现(Basic认证与自定义)和日志级别调整。最后,总结了`@FeignClient`常用属性,帮助开发者更好地理解和使用Feign进行微服务间通信。
169 1
|
4月前
|
前端开发 Java 数据库连接
Spring框架初识
Spring 是一个分层的轻量级开源框架,核心功能包括控制反转(IOC)和面向切面编程(AOP)。主要模块有核心容器、Spring 上下文、AOP、DAO、ORM、Web 模块和 MVC 框架。它通过 IOC 将配置与代码分离,简化开发;AOP 提供了声明性事务管理等增强功能。
119 21
Spring框架初识
|
2月前
|
缓存 监控 Java
java动态代理
本文介绍了Java中的动态代理及其优势,通过增强原有方法或拦截调用实现无侵入式代码扩展,如添加日志、缓存等。文章先讲解了静态代理的基本概念和实现方式,随后引出动态代理解决静态代理在多方法、多类场景下的局限性。通过JDK提供的InvocationHandler接口和Proxy类,展示了如何动态生成代理对象。最后,文章还探讨了代理Hook技术,包括寻找Hook点、选择代理方式以及替换原始对象的具体步骤。
|
4月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片
|
3月前
|
SQL 负载均衡 关系型数据库
MySQL复制以及调优
本文介绍了MySQL自带复制方案的实现及其注意事项。复制方案能提供数据备份、负载均衡与分布式数据管理的优势。文章详细描述了复制步骤:主库(master)记录更改到二进制日志,发送同步消息给从库(slave),从库接收后将日志复制到本地并执行。实现复制包括配置主库的server-id和二进制日志、创建复制账号、初始化主库数据、设置从库参数及开启复制。此外,还探讨了三种日志格式(row、statement、mixed)的特点及选择建议,并分析了主从复制延迟的优化方法,如控制事务大小、优化日志传输和多线程还原日志等。最后,文中列出了搭建过程中需要注意的关键点。
|
4月前
|
SQL 存储 关系型数据库
int(1) 和 int(10) 有什么区别?
在MySQL中,`int`类型后面的数字(如`int(1)`、`int(10)`)并不影响其存储范围,最大值仍为4294967295(无符号)。这些数字只有在配合`zerofill`使用时才有意义,用于显示时不足位数补0。例如,`int(4) zerofill`会将1显示为0001。这适用于需要固定长度编号的场景,如学号等。
114 3
int(1) 和 int(10) 有什么区别?
|
5月前
|
Shell Linux Ruby
Python3虚拟环境venv
`venv` 是 Python 的虚拟环境工具,用于为不同项目创建独立的运行环境,避免依赖冲突。通过 `python3 -m venv` 命令创建虚拟环境,并使用 `source bin/activate` 激活。激活后,所有 Python 包将安装在该环境中,不影响系统全局环境。退出环境使用 `deactivate` 命令。每个虚拟环境拥有独立的包集合,确保项目间的隔离性。删除虚拟环境只需删除其目录即可。
173 34