Go 语言 select 的实现原理

news2025/1/19 6:22:33

介绍

select是Go在语言层面提供的I/O多路复用的机制,其专门用来让Goroutine同时等待多个channel是否准备完毕:可读或可写。在Channel状态改变之前,select会一直阻塞当前线程或者goroutine。

特性:

case 必须是一个通信操作,主要是指对通道(Channel)进行发送或者接收数据的操作。

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

select 语句中如果没有 default 语句,则会阻塞等待任意一个 case满足执行条件。

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

当 select 中的多个 case 同时被触发时,会随机执行其中的一个。

普通多线程

多路复用

 数据结构

select在Go语言的源代码中不存在对应的结构体,使用runtime.scase 结构体表示select控制结构里的case。

type scase struct {
	c    *hchan                    //case操作的通道     
 
    kind  uint16
    //表示该case的类型,分为读channel、写channel和default。
    //读channel、写channel和default三种类型分别由常量定义
    //caseRecv:case语句中尝试读取scase.c中的数据。
    //caseSend:case语句中尝试向scase.c中写入数据。
    //caseDefault:default语句。
    
                                 
	elem unsafe.Pointer 
    //scase.kind == caseRecv : scase.elem表示读出channel的数据存放地址;
    //scase.kind == caseSend : scase.elem表示将要写入channel的数据存放地址;
}

在select语句运行时,scase结构体的实例会被用来表示每个case。运行时根据c 字段找到对应的通道,根据elem字段来处理数据的发送或接收操作。

执行流程

 实现过程和结果(穿插编译器的重写和优化)

单分支的select

只有一个 case 且不是 default,这种情况编译器会直接将其翻译成对管道的收发操作,并且还是阻塞式的,一直阻塞到操作可以完成。

对于接收操作(如 case val := <-ch),它会被转换为 val := <-ch,直接尝试从通道 ch 接收数据。

对于发送操作(如 case ch <- value),它会被转换为 ch <- value,直接尝试向通道 ch 发送数据。

只包含default分支会直接执行default操作。

多路select

在编译器中会被转换为runtime.selectgo函数调用。

func selectgo(cas0 *scase, order0 *uint16, , ncases int) (int, bool) {
    pollorder := order1[:ncases:ncases]
    lockorder := order1[ncases:][:ncases:ncases]
    for i := 1; i < ncases; i++ {
        j := fastrandn(uint32(i + 1))
        pollorder[i] = pollorder[j]
        pollorder[j] = uint16(i)
    }
    // 代码可能继续执行后续操作
}
  • cas0,scase数组的头部指针,前半部分存放的是写管道 case,后半部分存放的读管道 case,以nsends来区分

  • order0,它的长度是scase数组的两倍,前半部分分配给pollorder数组(决定管道执行顺序),后半部分分配给lockorder数组(决定管道锁定顺序)

  • pollorder:每次selectgo执行都会把scase序列打乱,以达到随机检测case的目的。

  • lockorder:所有case语句中channel序列,以达到去重防止对channel加锁时重复加锁的目的。

  • ncases表示scase数组的长度

直接阻塞

1. select结构不包含任何case

在Go编译器内部的代码如下

func walkselectcases(cases *Nodes) []*Node {
	n := cases.Len()

	if n == 0 {
		return []*Node{mkcall("block", nil, nil)}
	}
	...
}

func block() {
	gopark(nil, nil, waitReasonSelectNoCases, traceEvGoStop, 1)
}

walkselectcases的参数是一个select语句中的case元素的集合。当集合的长度为0时,表示当前select中无case会调用block函数,block函数会调用gopark让出goroutine对处理器的使用权并传入等待原因,暂停goroutine避免CPU空转。

2.当case中的channel是空指针

例:包含一个case且case中的channel是空指针,编译器会将select改写为if条件语句

//部分代码
if ch == nil {
    block()调用block,将goroutine陷入永久休眠
}

非阻塞操作 

当select中包含default分支时,就会被编译器认为是一次非阻塞的收发操作。

示例:一个case分支一个default分支,对通道的读写操作

示例:一个case分支一个default分支,对通道的读写操作

写操作:编译器会使用条件语句和 runtime.selectnbsend 函数改写代码

//改写
if selectnbsend(ch, i) {
    ...
} else {
    ...
}
//false参数决定了这一次的发送是非阻塞的,所以如果存在缓冲区空间不足时,当前 Goroutine 都不会阻塞而是会直接返回。
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
	return chansend(c, elem, false, getcallerpc())
}

 读操作:

// 改写前
select {
case v <- ch: // case v, ok <- ch:
    ......
default:
    ......
}

// 改写后
if selectnbrecv(&v, ch) { // if selectnbrecv2(&v, &ok, ch) {
    ...
} else {
    ...
}
//看读操作是否需要,第一个会忽略返回的布尔值,第二个会将布尔值传给调用方,block参数决定本次操作不阻塞
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {
	selected, _ = chanrecv(c, elem, false)
	return
}

func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
	selected, *received = chanrecv(c, elem, false)
	return
}

 性能优化建议

  1. case数量控制建议不超过5-10个
  2. 适当使用带缓冲区的channel避免频繁的阻塞和唤醒
  3. 合理使用default避免无谓的阻塞

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

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

相关文章

Java 视频处理:基于 MD5 校验秒传及 ffmpeg 切片合并的实现

本文介绍两种网络技术实现方法。一是 MD5 校验秒传&#xff0c;服务器端用数据库记上传文件 MD5 值及存储路径&#xff0c;Java 代码接收客户端 MD5 值并查询校验&#xff0c;返回状态码。二是用 ffmpeg 切片视频成 m3u8 上传&#xff0c;异步合并文件实现视频按需加载。 1. …

一文读懂iOS中的Crash捕获、分析以及防治

Crash系统性总结 Crash捕获与分析Crash收集符号化分析 Crash类别以及解法分析子线程访问UI而导致的崩溃unrecognized selector send to instance xxxKVO crashKVC造成的crashNSTimer导致的Crash野指针Watch Dog超时造成的crash其他crash待补充 参考文章&#xff1a; 对于iOS端开…

RK3576 Android14 状态栏和导航栏增加显示控制功能

问题背景&#xff1a; 因为RK3576 Android14用户需要手动控制状态栏和导航栏显示隐藏控制&#xff0c;包括对锁屏后下拉状态栏的屏蔽&#xff0c;在设置功能里增加此功能的控制&#xff0c;故参考一些博客完成此功能&#xff0c;以下是具体代码路径的修改内容。 解决方案&…

【Rust自学】13.5. 迭代器 Pt.1:迭代器的定义、iterator trait和next方法

13.5.0. 写在正文之前 Rust语言在设计过程中收到了很多语言的启发&#xff0c;而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。 在本章中&#xff0c;我们会讨论 Rust 的一…

LabVIEW 蔬菜精密播种监测系统

在当前蔬菜播种工作中&#xff0c;存在着诸多问题。一方面&#xff0c;播种精度难以达到现代农业的高标准要求&#xff0c;导致种子分布不均&#xff0c;影响作物的生长发育和最终产量&#xff1b;另一方面&#xff0c;对于小粒径种子&#xff0c;传统的监测手段难以实现有效监…

2024年年终总结——坎坷与坚持,焦虑与收获

不知不觉间&#xff0c;2024年已经悄然过去&#xff0c;回望这一年的时间&#xff0c;一时间竟感觉混混沌沌无法形容&#xff0c;选择一些时间坐下来让自己简单回忆一下自己的2024。 先简单回望一下24年一整年的工作情况&#xff1a; 24年一开始&#xff0c;工作最期待的的节点…

无人机技术架构剖析!

一、飞机平台系统 飞机平台系统是无人机飞行的主体平台&#xff0c;主要提供飞行能力和装载功能。它由机体结构、动力装置、电气设备等组成。 机体结构&#xff1a;无人机的机身是其核心结构&#xff0c;承载着其他各个组件并提供稳定性。常见的机身材料包括碳纤维、铝合金、…

springboot基于微信小程序的传统美食文化宣传平台小程序

Spring Boot 基于微信小程序的传统美食文化宣传平台 一、平台概述 Spring Boot 基于微信小程序的传统美食文化宣传平台是一个集传统美食展示、文化传承、美食制作教程分享、用户互动交流以及美食相关活动推广为一体的综合性线上平台。它借助 Spring Boot 强大的后端开发框架构…

Android系统开发(八):从麦克风到扬声器,音频HAL框架的奇妙之旅

引言&#xff1a;音浪太强&#xff0c;我稳如老 HAL&#xff01; 如果有一天你的耳机里传来的不是《咱们屯里人》&#xff0c;而是金属碰撞般的杂音&#xff0c;那你可能已经感受到了 Android 音频硬件抽象层 (HAL) 出问题的后果&#xff01;在 Android 音频架构中&#xff0c…

51.WPF应用加图标指南 C#例子 WPF例子

完整步骤&#xff1a; 先使用文心一言生成一个图标如左边使用Windows图片编辑器编辑&#xff0c;去除背景使用正方形&#xff0c;放大图片使图标铺满图片使用格式工程转换为ico格式&#xff0c;分辨率为最大 在资源管理器中右键项目添加ico类型图片到项目里图片属性设置为始终…

运行fastGPT 第四步 配置ONE API 添加模型

上次已经装好了所有的依赖和程序。 下面在网页中配置One API &#xff0c;这个是大模型的接口。配置好了之后&#xff0c;就可以配置fastGPT了。 打开 OneAPI 页面 添加模型 这里要添加具体的付费模型的API接口填进来。 可以通过ip:3001访问OneAPI后台&#xff0c;**默认账号…

道旅科技借助云消息队列 Kafka 版加速旅游大数据创新发展

作者&#xff1a;寒空、横槊、娜米、公仪 道旅科技&#xff1a;科技驱动&#xff0c;引领全球旅游分销服务 道旅科技 &#xff08;https://www.didatravel.com/home&#xff09; 成立于 2012 年&#xff0c;总部位于中国深圳&#xff0c;是一家以科技驱动的全球酒店资源批发商…

51单片机——DS18B20温度传感器

由于DS18B20数字温度传感器是单总线接口&#xff0c;所以需要使用51单片机的一个IO口模拟单总线时序与DS18B20通信&#xff0c;将检测的环境温度读取出来 1、DS18B20模块电路 传感器接口的单总线管脚接至单片机P3.7IO口上 2、DS18B20介绍 2.1 DS18B20外观实物图 管脚1为GN…

Redis的安装和配置、基本命令

一、实验目的 本实验旨在帮助学生熟悉Redis的安装、配置和基本使用&#xff0c;包括启动Redis服务、使用命令行客户端进行操作、配置Redis、进行多数据库操作以及掌握键值相关和服务器相关的命令。 二、实验环境准备 1. JAVA环境准备&#xff1a;确保Java Development Kit …

2、ansible的playbook

ansible的脚本&#xff1a;playbook剧本 脚本的作用&#xff1a;复用 playbook的组成部分 1、开头 ---&#xff1a;表示是一个yaml文件&#xff0c;但是可以忽略。 2、Tasks&#xff08;任务&#xff09;&#xff1a;包含了目标主机上执行的操作&#xff0c;操作还是由模板来…

vscode的安装与使用

下载 地址&#xff1a;https://code.visualstudio.com/ 安装 修改安装路径&#xff08;不要有中文&#xff09; 点击下一步&#xff0c;创建桌面快捷方式&#xff0c;等待安装 安装中文插件 可以根据自己的需要安装python和Jupyter插件

Java : 各版本 jdk 下载及环境变量配置

--------------------------一、 JDK下载 ---------------------------- JDK下载地址&#xff1a;&#xff08;没有账号提示注册&#xff0c;最好用Chrome 浏览器&#xff09; Java Archive | Oracle 选择版本安装&#xff1a;&#xff08;注意不同系统&#xff09; 下载后按照…

IoTDB 查询时报可用内存不足

现象 IoTDB 3C3D 集群中&#xff0c;进行查询时报可用内存不足&#xff0c;即使是 show devices 这样简单的查询也会报内存不足。 原因 客户目前使用的 JDK 版本是 1.8, 该版本 JDK 对 GC 控制效果不佳&#xff0c;有可能出现可用内存不足的情况&#xff0c;同时 GC 耗时较长…

Jmeter 简单使用、生成测试报告(一)

一、下载Jmter 去官网下载&#xff0c;我下载的是apache-jmeter-5.6.3.zip&#xff0c;解压后就能用。 二、安装java环境 JMeter是基于Java开发的&#xff0c;运行JMeter需要Java环境。 1.下载JDK、安装Jdk 2.配置java环境变量 3.验证安装是否成功&#xff08;java -versio…

LabVIEW时域近场天线测试

随着通信技术的飞速发展&#xff0c;特别是在5G及未来通信技术中&#xff0c;天线性能的测试需求日益增加。对于短脉冲天线和宽带天线的时域特性测试&#xff0c;传统的频域测试方法已无法满足其需求。时域测试方法在这些应用中具有明显优势&#xff0c;可以提供更快速和精准的…