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

2024-06-20 20:38

本文主要是介绍由浅入深聊聊Golang中select的实现机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

正文

话说今天在玩select的时候发现一个问题,是这样的:

片段1:

func main(){var count intfor {select {case <-time.Tick(time.Millisecond * 500):fmt.Println("咖啡色的羊驼")count++fmt.Println("count--->" , count)case <-time.Tick(time.Millisecond * 499) :fmt.Println(time.Now().Unix())count++fmt.Println("count--->" , count)}}
}

片段2:

func main(){t1 := time.Tick(time.Second)t2 := time.Tick(time.Second)var count intfor {select {case <-t1:fmt.Println("咖啡色的羊驼")count++fmt.Println("count--->" , count)case <-t2 :fmt.Println(time.Now().Unix())count++fmt.Println("count--->" , count)}}
}

两个问题:
1.以上片段的输出结果是?
2.如何解释?

第一个问题好解决,跑一下就是,很明显输出结果肯定不同。
片段1:

1535673600
count---> 1
1535673600
count---> 2
1535673601
count---> 3

片段2:

咖啡色的羊驼
count---> 1
1535673600
count---> 2
咖啡色的羊驼
count---> 3
1535673601
count---> 4

第二个好理解,因为select监听了两个time的通道,所以交替出现。
那么第一个为何只有出现1个?
为了这个问题不得不把select的实现机制走一波,所以有了此文。

select机制简述

select有这么几个需要关注的机制
1.select+case是用于阻塞监听goroutine的,如果没有case,就单单一个select{},则为监听当前程序中的goroutine,此时注意,需要有真实的goroutine在跑,否则select{}会报panic

2.select底下有多个可执行的case,则随机执行一个。

3.select常配合for循环来监听channel有没有故事发生。需要注意的是在这个场景下,break只是退出当前select而不会退出for,需要用break TIP / goto的方式。

4.无缓冲的通道,则传值后立马close,则会在close之前阻塞,有缓冲的通道则即使close了也会继续让接收后面的值

5.同个通道多个goroutine进行关闭,可用recover panic的方式来判断通道关闭问题

看完以上知识点其实还是没法解释本文的核心疑惑,继续往下!

select机制详解

select的机制可以查看/src/runtime/select.go来了解。

源码片段解读:

func selectgo(sel *hselect) int {// ...// case洗牌pollslice := slice{unsafe.Pointer(sel.pollorder), int(sel.ncase), int(sel.ncase)}pollorder := *(*[]uint16)(unsafe.Pointer(&pollslice))for i := 1; i < int(sel.ncase); i++ {//....}// 给case排序lockslice := slice{unsafe.Pointer(sel.lockorder), int(sel.ncase), int(sel.ncase)}lockorder := *(*[]uint16)(unsafe.Pointer(&lockslice))for i := 0; i < int(sel.ncase); i++ {// ...}for i := int(sel.ncase) - 1; i >= 0; i-- {// ...}// 加锁该select中所有的channelsellock(scases, lockorder)// 进入loop
loop:// ... // pass 1 - look for something already waiting// 按顺序遍历case来寻找可执行的casefor i := 0; i < int(sel.ncase); i++ {//...switch cas.kind {case caseNil:continuecase caseRecv:// ... goto xxxcase caseSend:// ... goto xxxcase caseDefault:dfli = casidfl = cas}}// 没有找到可以执行的case,但有default条件,这个if里就会直接退出了。if dfl != nil {// ...}// ...// pass 2 - enqueue on all chans// chan入等待队列for _, casei := range lockorder {// ...switch cas.kind {case caseRecv:c.recvq.enqueue(sg)case caseSend:c.sendq.enqueue(sg)}}// wait for someone to wake us up// 等待被唤起,同时解锁channel(selparkcommit这里实现的)gp.param = nilgopark(selparkcommit, nil, "select", traceEvGoBlockSelect, 1)// 突然有故事发生,被唤醒,再次该select下全部channel加锁sellock(scases, lockorder)// pass 3 - dequeue from unsuccessful chans// 本轮最后一次循环操作,获取可执行case,其余全部出队列丢弃casi = -1cas = nilsglist = gp.waiting// Clear all elem before unlinking from gp.waiting.for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink {sg1.isSelect = falsesg1.elem = nilsg1.c = nil}gp.waiting = nilfor _, casei := range lockorder {// ...if sg == sglist {// sg has already been dequeued by the G that woke us up.casi = int(casei)cas = k} else {c = k.cif k.kind == caseSend {c.sendq.dequeueSudoG(sglist)} else {c.recvq.dequeueSudoG(sglist)}}// ...}// 没有的话,再走一次loopif cas == nil {goto loop}// ...
bufrecv:// can receive from buffer
bufsend:// ...
recv:// ...
rclose:// ...
send:// ...
retc:// ...
sclose:// send on closed channel
}

为了方便展示,专门搞了一张很丑的图,来说明流程:

这里写图片描述

大概就是说呢,select是分四步进行的。

本文的疑惑关键点就在于那个loop的时候,当接收到发现一个可执行的时候,本次select不会执行的那些case对应的channel给出队当前goroutine,就不管他们了,就丢了,由于time.Tick是现场在case里头创建的,而不是像片段二是处于全局栈中,所以当每次任何一个执行的时候,另一个就被抛弃了,再次selelct的时候有需要重新获取,又是新的需要重头再来。

本人暂时的理解是这样,如果你有更好的理解,请给我留言,谢谢。

如果你觉得有收获~可以关注我的公众号【咖啡色的羊驼】~第一时间收到我的分享和知识梳理~
在这里插入图片描述

这篇关于由浅入深聊聊Golang中select的实现机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1079195

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

基于Springboot + vue 的抗疫物质管理系统的设计与实现

目录 📚 前言 📑摘要 📑系统流程 📚 系统架构设计 📚 数据库设计 📚 系统功能的具体实现    💬 系统登录注册 系统登录 登录界面   用户添加  💬 抗疫列表展示模块     区域信息管理 添加物资详情 抗疫物资列表展示 抗疫物资申请 抗疫物资审核 ✒️ 源码实现 💖 源码获取 😁 联系方式 📚 前言 📑博客主页:

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议

Linux系统稳定性的奥秘:探究其背后的机制与哲学

在计算机操作系统的世界里,Linux以其卓越的稳定性和可靠性著称,成为服务器、嵌入式系统乃至个人电脑用户的首选。那么,是什么造就了Linux如此之高的稳定性呢?本文将深入解析Linux系统稳定性的几个关键因素,揭示其背后的技术哲学与实践。 1. 开源协作的力量Linux是一个开源项目,意味着任何人都可以查看、修改和贡献其源代码。这种开放性吸引了全球成千上万的开发者参与到内核的维护与优化中,形成了

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python

Spring中事务的传播机制

一、前言 首先事务传播机制解决了什么问题 Spring 事务传播机制是包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。 事务的传播级别有 7 个,支持当前事务的:REQUIRED、SUPPORTS、MANDATORY; 不支持当前事务的:REQUIRES_NEW、NOT_SUPPORTED、NEVER,以及嵌套事务 NESTED,其中 REQUIRED 是默认的事务传播级别。