前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >由浅入深聊聊Golang中select的实现机制

由浅入深聊聊Golang中select的实现机制

作者头像
会呼吸的Coder
发布于 2020-02-17 09:51:30
发布于 2020-02-17 09:51:30
1.5K00
代码可运行
举报
文章被收录于专栏:会呼吸的Coder会呼吸的Coder
运行总次数:0
代码可运行

select是go语言中常用的一个关键字,其用法也一直被用作面试题来考核应聘者。今天,结合代码来分析下select的主要用法。

首先,我们来从官方文档看一下有关select的描述:

A "select" statement chooses which of a set of possible send or receive operations will proceed. It looks similar to a "switch" statement but with the cases all referring to communication operations. 一个select语句用来选择哪个case中的发送或接收操作可以被立即执行。它类似于switch语句,但是它的case涉及到channel有关的I/O操作。

或者换一种说法,select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作。

基本用法

代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//select基本用法
select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:

// 如果上面都没有成功,则进入default处理流程
代码语言:javascript
代码运行次数:0
运行
复制

Execution of a "select" statement proceeds in several steps: 1.For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated. 所有channel表达式都会被求值、所有被发送的表达式都会被求值。求值顺序:自上而下、从左到右. 结果是选择一个发送或接收的channel,无论选择哪一个case进行操作,表达式都会被执行。RecvStmt左侧短变量声明或赋值未被评估。

  1. If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed. 如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行.

3.Unless the selected case is the default case, the respective communication operation is executed. 除非所选择的情况是默认情况,否则执行相应的通信操作。 4.If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned. 如果所选case是具有短变量声明或赋值的RecvStmt,则评估左侧表达式并分配接收值(或多个值)。 5.The statement list of the selected case is executed. 执行所选case中的语句

案例分析

案例1 如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
start := time.Now()
    c := make(chan interface{})
    ch1 := make(chan int)
        ch2 := make(chan int)

    go func() {

        time.Sleep(4*time.Second)
        close(c)
    }()

    go func() {

        time.Sleep(3*time.Second)
        ch1 <- 3
    }()

      go func() {

        time.Sleep(3*time.Second)
        ch2 <- 5
    }()

    fmt.Println("Blocking on read...")
    select {
    case <- c:

        fmt.Printf("Unblocked %v later.\n", time.Since(start))

    case <- ch1:

        fmt.Printf("ch1 case...")
      case <- ch2:

        fmt.Printf("ch1 case...")
    default:

        fmt.Printf("default go...")
    }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
运行上述代码,由于当前时间还未到3s。所以,目前程序会走default
代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Blocking on read...
default go...

Process finished with exit code 0
修改代码,将default注释://default:

 //       fmt.Printf("default go...")
这时,select语句会阻塞,直到监测到一个可以执行的IO操作为止。这里,先会执行完睡眠3s的gorountine,此时两个channel都满足条件,这时系统会随机选择一个case继续操作。Blocking on read...
ch2 case...

Process finished with exit code 0
代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
go func() {

        time.Sleep(5*time.Second)
        ch1 <- 3
    }()
go func() {

        time.Sleep(5*time.Second)
        ch2 <- 3
    }()
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
此时会先执行到上面的gorountine,select执行的就是c的case
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Process finished with exit code
代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var ch1 chan int
var ch2 chan int
var chs = []chan int{ch1, ch2}
var numbers = []int{1, 2, 3, 4, 5}

func main () {

    select {
    case getChan(0) <- getNumber(2):

        fmt.Println("1th case is selected.")
    case getChan(1) <- getNumber(3):

        fmt.Println("2th case is selected.")
    default:

        fmt.Println("default!.")
        }
        }

func getNumber(i int) int {
    fmt.Printf("numbers[%d]\n", i)

    return numbers[i]
}
func getChan(i int) chan int {
    fmt.Printf("chs[%d]\n", i)

    return chs[i]
}
代码语言:javascript
代码运行次数:0
运行
复制

此时,select语句走的是default操作。但是这时每个case的表达式都会被执行。以case1为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
case getChan(0) <- getNumber(2):
代码语言:javascript
代码运行次数:0
运行
复制

系统会从左到右先执行getChan函数打印chs[0],然后执行getNumber函数打印numbers[2]。同样,从上到下分别执行所有case的语句。所以,程序执行的结果为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
chs[0]
numbers[2]
chs[1]
numbers[3]
default!.
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


Process finished with exit code 0
示例3 break关键字结束select
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)

    ch1 <- 3
    ch2 <- 5

    select {
    case <- ch1:

        fmt.Println("ch1 selected.")

        break

        fmt.Println("ch1 selected after break")
    case <- ch2:

        fmt.Println("ch2 selected.")
        fmt.Println("ch2 selected without break")
    }
代码语言:javascript
代码运行次数:0
运行
复制

很明显,ch1和ch2两个通道都可以读取到值,所以系统会随机选择一个case执行。我们发现选择执行ch1的case时,由于有break关键字只执行了一句:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Process finished with exit code
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Process finished with exit code 0
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 初级程序员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Go】留意 Select 的预求值!
关键在于 recvAndSend 函数的 case ch1 <- <-ch2: 我们希望在一条 case 中从 chan2 中取出数据并放到 chan1 中,但事实上这样会导致死锁,虽然平时谁也不会写出这种神仙代码,但下面这个就很容易被写出来了:
JuneBao
2022/10/26
2120
go语言select语句中的求值问题
这…这是欺负我不懂英文么…如此翻译,隐去了太多细节!要想理解这段话,我们用下图来对齐下概念:
跑马溜溜的球
2021/09/23
6790
go语言select语句中的求值问题
Golang深入浅出之-Select语句在Go并发编程中的应用
在Go语言的并发编程世界中,select语句扮演着至关重要的角色,它为Go程序员提供了优雅且高效的通道通信控制机制。本文将深入浅出地探讨select语句的基本用法、常见问题、易错点以及如何有效避免这些问题,辅以代码示例,帮助您更深入地理解和掌握这一强大的工具。
Jimaks
2024/04/26
2070
学会 Go select 语句,轻松实现高效并发
在 Go 语言中,Goroutine 和 Channel 是非常重要的并发编程概念,它们可以帮助我们解决并发编程中的各种问题。关于它们的基本概念和用法,前面的文章 一文初探 Goroutine 与 channel 中已经进行了介绍。而本文将重点介绍 select,它是协调多个 channel 的桥梁。
陈明勇
2023/10/15
8200
学会 Go select 语句,轻松实现高效并发
golang学习之select用法
本文介绍了golang中select函数的用法,包括监控文件描述符、超时控制、读取超时的实现以及使用select判断channel是否存满。
用户1141560
2017/12/26
1.5K0
Golang 基础之基础语法梳理 (二)
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义,channel就是它们之间的连接。
帽儿山的枪手
2022/03/20
6960
Golang 基础之基础语法梳理 (二)
select详解
在某些场景下需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。也许会写出如下代码使用遍历的方式来实现:
Michel_Rolle
2023/11/30
2.6K0
16.Go语言-Select
上面的程序创建了 3 个通道,并在执行 select 语句之前往通道 1 、通道 2 和 通道 3 分别发送数据,在执行 select 语句时,如果有机会的话会运行所有表达式,只要其中一个通道接收到数据,那么就会执行对应的 case 代码,然后退出。所以运行该程序可能输出下面的语句:
面向加薪学习
2022/09/04
2710
Golang之旅23-通道channel
在goroutine并发执行的时候,需要在函数和函数之间进行通信。Go语言并发模式CSP(communicating Sequents Processes),通过通信共享内存。
皮大大
2021/03/02
3430
A Bite of GoLang(下)
8. Goroutine 8.0、Goroutine介绍 协程 Coroutine 轻量级"线程" 上面的两个特征到底是什么意思呢?下面我们通过具体的事例详细的讲述一下, package main
盛国存
2018/05/14
1.1K1
A Bite of GoLang(下)
Golang中的管道(channel) 、goroutine与channel实现并发、单向管道、select多路复用以及goroutine panic处理
管道(channel)是 Go 语言中实现并发的一种方式,它可以在多个 goroutine 之间进行通信和数据交换。管道可以看做是一个队列,通过它可以进行先进先出的数据传输,支持并发的读和写。
周小末天天开心
2023/10/16
7040
Golang中的管道(channel) 、goroutine与channel实现并发、单向管道、select多路复用以及goroutine panic处理
Golang 25个保留关键字
Go共有25个保留关键字,各有其作用,不能用作标识符。Go的25个关键字按照作用可以分为3类,分别为包管理、程序实体声明与定义与程序流程控制。
恋喵大鲤鱼
2019/06/14
3.4K0
10.Go-goroutine,waitgroup,互斥锁,channel和select
互斥锁表示锁的代码同一时间只能有一个goroutine运行,而读写锁表示在锁范围内数据的读写操作
zhang_derek
2019/08/12
7870
《快学 Go 语言》第 12 课 —— 神秘的地下通道
不同的并行协程之间交流的方式有两种,一种是通过共享变量,另一种是通过队列。Go 语言鼓励使用队列的形式来交流,它单独为协程之间的队列数据交流定制了特殊的语法 —— 通道。
老钱
2018/12/24
4080
《快学 Go 语言》第 12 课 —— 神秘的地下通道
go channel 管道
协程是并发编程的基础,而管道(channel)则是并发中协程之间沟通的桥梁,很多时候我们启动一个协程去执行完一个操作,执行操作之后我们需要返回结果,或者多个协程之间需要相互协作。
看、未来
2022/06/19
6940
go channel 管道
GoLang协程与通道---中
通道可以被显式的关闭;尽管它们和文件不同:不必每次都关闭。只有在当需要告诉接收者不会再提供新的值的时候,才需要关闭通道。只有发送者需要关闭通道,接收者永远不会需要。
大忽悠爱学习
2022/08/23
8390
go channel 使用及机制流程汇总
makechan()初始化hchan结构体, 如果没有缓冲区即分配hchanSize大小的内存并返回;而有缓冲区的情况下, 则计算管道元素类型大小并分配hchanSize+(elem.size * size)大小的内存(缓冲区是一个环形的结构设计), 最后返回hchan.
会呼吸的Coder
2020/02/17
4340
go语言学习-并发编程
1.After函数:起到定时器的作用,指定的纳秒后会向返回的channel中放入一个当前时间(time.Time)的实例。
solate
2019/07/22
6220
《Go小技巧&易错点100例》第二十八篇
在 Go 语言中,runtime.Caller(1) 是 runtime 包提供的一个函数,用于获取当前 goroutine 的调用堆栈中的特定调用者的信息。这里的 1 表示要跳过的调用帧数。具体来说,当你调用 runtime.Caller(1) 时,它会返回调用 runtime.Caller 的函数的调用者的信息。
闫同学
2025/01/23
680
Go-并发编程-使用 select 语句实现多路复用(二)
在上面的示例中,我们启动了两个goroutine分别向两个通道中发送数据。然后我们将select语句放在一个无限循环中,以持续监听这两个通道的状态。
堕落飞鸟
2023/04/21
2290
相关推荐
【Go】留意 Select 的预求值!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档