Go select 语句使用场景

1. select介绍

select 是 Go 语言中的一种控制结构,用于在多个通信操作中选择一个可执行的操作。它可以协调多个 channel 的读写操作,使得我们能够在多个 channel 中进行非阻塞的数据传输、同步和控制。

基本语法:

select {case communication clause  :statement(s);case communication clause  :statement(s);/* 你可以定义任意数量的 case */default : /* 可选 */statement(s);
}

如果多个 case 都可以运行,select 会随机公平地选出一个执行。如果没有 case 可以运行,它要么阻塞(等待 case),要么执行default子句。

2. select 语句的常用使用场景:

  • 等待多个通道的消息(多路复用)

    当我们需要等待多个通道的消息时,使用 select 语句可以非常方便地等待这些通道中的任意一个通道有消息到达,从而避免了使用多个goroutine进行同步和等待。

  • 超时等待通道消息

    当我们需要在一段时间内等待某个通道有消息到达时,使用 select 语句可以与 time 包结合使用实现定时等待。

  • 在通道上进行非阻塞读写

    在使用通道进行读写时,如果通道没有数据,读操作或写操作将会阻塞。但是使用 select 语句结合 default 分支可以实现非阻塞读写,从而避免了死锁或死循环等问题。

因此,select 的主要作用是在处理多个通道时提供了一种高效且易于使用的机制,简化了多个 goroutine 的同步和等待,使程序更加可读、高效和可靠。

3. 代码示例:

代码1:

package mainimport ("fmt""time"
)func main() {chan1 := make(chan int)chan2 := make(chan int)go func() {chan1 <- 1time.Sleep(5 * time.Second)}()go func() {chan2 <- 1time.Sleep(5 * time.Second)}()select {case <-chan1:fmt.Println("chan1")case <-chan2:fmt.Println("chan2")default:fmt.Println("default")}fmt.Println("main exit")
}

输出结果为:

可能会出现三种结果:

chan1
main exit
chan2
main exit
default
main exit

select 中的 case 执行顺序是随机的,如果某个 case 中的 channel 已经 ready,那么就会执行相应的语句并退 出 select 流程,如果所有 case 中的 channel 都未 ready,那么就会执行 default 中的语句然后退出 select 流程。

由于启动的协程和 select 语句并不能保证执行的顺序,所以也有可能 select 执行时协程还未向channel中写入数据,所以 select 直接执行 default 语句并退出。因此,程序有可能产生三种输出

代码2:

package mainimport ("fmt"
)func main() {chan1 := make(chan int)chan2 := make(chan int)go func() {close(chan1)}()go func() {close(chan2)}()select {case <- chan1:fmt.Println("chan1")case <- chan2:fmt.Println("chan2")}fmt.Println("main exit.")
}

select 会随机检测各 case 语句中 channel 是否 ready,注意已关闭的 channel 也是可读的,所以上述程序中select 不会阻塞,具体执行哪个 case 语句是随机的。

代码3:

package mainfunc main() {select {}
}

对于空的 select 语句,程序会被阻塞,确切的说是当前协程被阻塞,同时 Go 自带死锁检测机制,当发现当前协程再也没有机会被唤醒时,则会发生 panic。所以上述程序会 panic。

定时器实现定时任务的执行代码:

package mainimport ("fmt""time"
)func main() {fmt.Println("定时任务开始")// 创建一个每秒触发一次的定时器ticker := time.NewTicker(1 * time.Second)done := make(chan bool)go func() {for {select {case <-ticker.C:fmt.Println("执行定时任务")case <-done:ticker.Stop()return}}}()// 等待5秒time.Sleep(5 * time.Second)done <- truefmt.Println("定时任务结束")
}

结果:

定时任务开始
执行定时任务
执行定时任务
执行定时任务
执行定时任务
执行定时任务
定时任务结束

超时退出实现代码:

package mainimport ("fmt""time"
)func main() {timeout := 5 * time.Seconddone := make(chan bool)go func() {// 模拟耗时操作time.Sleep(2 * time.Second)done <- true}()select {case <-done:fmt.Println("Task completed successfully.")case <-time.After(timeout):fmt.Println("Timeout! The operation took too long.")}
}

或:

package mainimport ("context""fmt""time"
)func main() {timeout := 5 * time.Secondctx, cancel := context.WithTimeout(context.Background(), timeout)defer cancel()done := make(chan bool)go func() {// 模拟耗时操作time.Sleep(2 * time.Second)done <- true}()select {case <-done:fmt.Println("Task completed successfully.")case <-ctx.Done():fmt.Println("Timeout! The operation took too long.")}
}

4. 总结

  • select 语句中除 default 外,每个 case 操作一个channel,要么读要么写

  • select语句中除 default 外,各 case 执行顺序是随机的

  • select 语句中如果没有 default 语句,则会阻塞等待任一 case

  • select 语句中读操作要判断是否成功读取,关闭的 channel 也可以读取

select在 Go 语言的源代码中不存在对应的结构体,只是定义了一个 runtime.scase 结构体(在src/runtime/select.go)表示每个 case 语句(包含defaut):

// Select case descriptor.
// Known to compiler.
// Changes here must also be made in src/cmd/compile/internal/walk/select.go's scasetype.
type scase struct {c    *hchan         // chanelem unsafe.Pointer // data element
}

因为所有的非 default 的 case 基本都要求是对Channel的读写操作,所以 runtime.scase 结构体中也包含一个 runtime.hchan 类型的字段存储 case 中使用的 Channel,另一个字段 elem 指向 case 条件包含的数据的指针,如 case ch1 <- 1,则 elem 指向常量1.

编译器会对select有不同的case的情况进行优化以提高性能。首先,编译器对select没有case、有单case和单case+default的情况进行单独处理,这些处理或者直接调用运行时函数,或者直接转成对channel的操作,或者以非阻塞的方式访问channel,多种灵活的处理方式能够提高性能,尤其是避免对channel的加锁。

对最常出现的select有多case的情况,会调用runtime.selectgo()函数来获取执行case的索引

func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool)

selectgo函数内部逻辑:

  1. 使用fastrandn算法把scases数组的索引重新编排顺序。

  2. 根据新的索引顺序对hchan进行堆排序来获取case的锁定顺序。(保证 n log n 时间和恒定的堆栈占用空间)

  3. 锁定所有channel。

  4. 遍历所有channel,判断是否有可读或者可写的,如果有,解锁channel,返回对应数据。

  5. 否则,判断有没有default,如果有,解锁channel,返回default对应scase。

  6. 否则,把当前groutian添加到所有channel的等待队列里,解锁所有channel,等待被唤醒。

  7. 被唤醒后,再次锁定所有channel

  8. 遍历所有channel,把g从channel等待队列中移除,并找到可操作的channel

  9. 如果对应的scase不为空,直接返回对应的值

  10. 否则循环此过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/24448.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024 cicsn SuperHeap

文章目录 参考沙箱存在protobuf逆向buy_booksee_bookreturn_bookedit_booksearch_book 思路exp 参考 https://hakuya.work/post/7 https://akaieurus.github.io/2024/05/20/2024%E5%9B%BD%E8%B5%9B%E5%88%9D%E8%B5%9Bpwn-wp/#SuperHeap https://blog.csdn.net/m0_63437215/art…

TSINGSEE青犀视频汇聚机房动环智能监控方案,提升机房安全稳定性

一、背景需求 在当今信息化时代&#xff0c;机房作为数据中心的核心设施&#xff0c;承载着重要的网络设备和数据存储设备&#xff0c;其正常运行对于企业的数据安全和业务连续性至关重要。机房内部设备众多&#xff0c;且运行过程中涉及大量的数据交换和传输。一旦发生安全事…

java 类加载器及双亲委派机制

1、 有哪些类加载器 还有自定义类加载器。最上面的为父加载器&#xff0c;加载类的路径是不一样的 2、 什么是双亲委派机制&#xff1a; 1. 加载时&#xff0c;先去找父类&#xff0c;父类无法加载时&#xff0c;在由儿子加载 3、 为什么用双亲委派&#xff1a; 沙箱安全&…

【devops】 Bytebase 一站式开源 数据库DevOps平台

初识 Bytebase 1、安装 安装地址 https://www.bytebase.com/docs/get-started/self-host/#docker 安装指令 docker run --init \--name bytebase \--publish 8080:8080 --pull always \--volume ~/.bytebase/data:/var/opt/bytebase \bytebase/bytebase:2.18.02、登录-dashboa…

OlSoul系统调校程序v2024.06.05

软件介绍 OlSoul是一款能够适配用于Win各个系统的系统调校软件&#xff0c;OlSoul内置有众多调校功能可以直接使用&#xff0c;如有启用无线网络功能、启用打印机功能、系统快速休眠与休眠开关、快捷方式小箭头去除功能等&#xff0c;具体的调校功能多达几十项&#xff0c;可自…

RT-DETR 详解之 Efficient Hybrid Encoder

在先前的博文中&#xff0c;博主介绍了RT-DETR在官方代码与YOLOv8集成程序中的训练与推理过程&#xff0c;接下来&#xff0c;博主将通过代码调试的方式来梳理RT-DETR的整个过程。 整体结构 RT-DETR的代码调试大家可以参考博主这篇文章&#xff1a; 在梳理整个代码之前&…

SpringBoot高手之路-原理篇

文章目录 JDK动态代理 JDK动态代理

长文预警:自动驾驶の核燃料库!Tesla数据标注系统解析

长文预警&#xff1a;自动驾驶の核燃料库&#xff01;Tesla数据标注系统解析 前言 本文整理自原文链接&#xff0c;写的非常好&#xff0c;给了博主很多启发&#xff0c;投原创是因为平台机制&#xff0c;希望能被更多人看到。 掐指一算&#xff0c;又到了该学习的时间&#…

Python | Leetcode Python题解之第137题只出现一次的数字II

题目&#xff1a; 题解&#xff1a; class Solution:def singleNumber(self, nums: List[int]) -> int:a b 0for num in nums:b ~a & (b ^ num)a ~b & (a ^ num)return b

56.WEB渗透测试-信息收集- 端口、目录扫描、源码泄露(4)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;55.WEB渗透测试-信息收集- 端口、目录扫描、源码泄露&#xff08;3&#xff09; 如果把文…

【PyTorch】PyTorch深度学习框架实战(二):torchrun

一、引言 PyTorch由facebook人工智能研究院研发&#xff0c;2017年1月被提出&#xff0c;是一个开源的Python机器学习库&#xff0c;基于Torch&#xff0c;用于自然语言处理等应用程序。PyTorch既可以看作加入了GPU支持的numpy&#xff0c;同时也可以看成一个拥有自动求导功能的…

SpringAI调用OpenAI Demo

Spring AI 在maven的setting.xml <mirror> <id>spring-milestones</id> <name>Spring Milestones</name> <mirrorOf>spring-milestones</mirrorOf> <url>https://repo.sprin…

51单片机-实机演示(LED点阵)

目录 前言: 一.线位置 二.扩展 三.总结 前言: 这是一篇关于51单片机实机LED点阵的插线图和代码说明.另外还有一篇我写的仿真的连接在这:http://t.csdnimg.cn/ZNLCl,欢迎大家的点赞,评论,关注. 一.线位置 接线实机图. 引脚位置注意: 1. *-* P00->RE8 P01->RE7 …

Android Kotlin 异步操作回调转换为挂起函数

异步接口回调是一种通过接口将任务的执行和结果处理分离开来的编程设计模式。通常用于网络请求、数据库查询等耗时操作。 挂起函数是 Kotlin 中的一个特性&#xff0c;用于简化异步编程。挂起函数是可以在协程中暂停执行并恢复的函数&#xff0c;避免了回调地狱问题&#xff0…

Golang | Leetcode Golang题解之第137题只出现一次的数字II

题目&#xff1a; 题解&#xff1a; func singleNumber(nums []int) int {a, b : 0, 0for _, num : range nums {b (b ^ num) &^ aa (a ^ num) &^ b}return b }

AI视频教程下载:生成式AI—从入门到精通

生成式人工智能正在彻底改变我们的生活。 本视频教程让您全面了解生成式人工智能的基本概念、模型、工具和应用&#xff0c;使您能够利用生成式人工智能的潜力&#xff0c;改善工作场所、事业和生活。 该视频教程由五门自定进度的短期课程组成&#xff0c;每门课程需要 3-5 个…

ChatGPT-4o在临床医学日常工作、数据分析与可视化、机器学习建模中的技术

2022年11月30日&#xff0c;可能将成为一个改变人类历史的日子——美国人工智能开发机构OpenAI推出了聊天机器人ChatGPT-3.5&#xff0c;将人工智能的发展推向了一个新的高度。2023年11月7日&#xff0c;OpenAI首届开发者大会被称为“科技界的春晚”&#xff0c;吸引了全球广大…

【中心拓展法-BM73 最长回文子串】

题目 BM73 最长回文子串 分析 中心拓展法&#xff1a; 中心有两种&#xff1a; 将字母作为中心&#xff08;starti,end i)&#xff0c;将字母后的间隙作为中心(start i, end i1) 此时要注意begin 和end之间字母数量的计算&#xff1a; begin 和end 之间的字母数量应为…

数据挖掘--数据预处理

数据清理 缺失值 如果数据集含有分类属性&#xff0c;一种简单的填补缺失值的方法为&#xff0c;将属于同一类的对象的该属性值的均值赋此缺失值&#xff1b;对于离散属性或定性属性&#xff0c;用众数代替均值。更复杂的方法&#xff0c;可以将其转换为分类问题或数值预测问…

再次修改了备忘录

Control <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>与妖为邻备忘录</title><!-- <…