Go语言之select的使用和实现原理
ztj100 2025-01-01 23:52 42 浏览 0 评论
select简介
1.Go的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的;当select中没有case语句的时候,会阻塞当前groutine。
2.select是Golang在语言层面提供的I/O多路复用的机制,其专门用来检测多个channel是否准备完毕:可读或可写。
3.select语句中除default外,每个case操作一个channel,要么读要么写
4.select语句中除default外,各case执行顺序是随机的
5.select语句中如果没有default语句,则会阻塞等待任一case
6.select语句中读操作要判断是否成功读取,关闭的channel也可以读取
转自:https://www.cnblogs.com/wuyepeng/p/13910678.html
参考:go语言中文文档:www.topgoer.com
用法例子
实例一:select语句中除default外,各case执行顺序是随机的
package main
import (
"fmt"
"time"
)
func main() {
chan1 := make(chan int)
chan2 := make(chan int)
go func() {
chan1 <- 1
time.Sleep(5 * time.Second)
}()
go func() {
chan2 <- 1
time.Sleep(5 * time.Second)
}()
select {
case <-chan1:
fmt.Println("chan1 ready.")
case <-chan2:
fmt.Println("chan2 ready.")
default:
fmt.Println("default")
}
fmt.Println("main exit.")
}
程序中声明两个channel,分别为chan1和chan2,依次启动两个协程,分别向两个channel中写入一个数据就进入睡眠。select语句两个case分别检测chan1和chan2是否可读,如果都不可读则执行default语句。
参考答案:
select中各个case执行顺序是随机的,如果某个case中的channel已经ready,则执行相应的语句并退出select流程,如果所有case中的channel都未ready,则执行default中的语句然后退出select流程。另外,由于启动的协程和select语句并不能保证执行顺序,所以也有可能select执行时协程还未向channel中写入数据,所以select直接执行default语句并退出。所以,以下三种输出都有可能:
可能的输出一:
chan1 ready.
main exit.
可能的输出二:
chan2 ready.
main exit.
可能的输出三:
default
main exit.
实例二:select语句中如果没有default语句,则会阻塞等待任一case
package main
import (
"fmt"
"time"
)
func main() {
chan1 := make(chan int)
chan2 := make(chan int)
writeFlag := false
go func() {
for {
if writeFlag {
chan1 <- 1
}
time.Sleep(time.Second)
}
}()
go func() {
for {
if writeFlag {
chan2 <- 1
}
time.Sleep(time.Second)
}
}()
select {
case <-chan1:
fmt.Println("chan1 ready.")
case <-chan2:
fmt.Println("chan2 ready.")
}
fmt.Println("main exit.")
}
程序中声明两个channel,分别为chan1和chan2,依次启动两个协程,协程会判断一个bool类型的变量writeFlag来决定是否要向channel中写入数据,由于writeFlag永远为false,所以实际上协程什么也没做。select语句两个case分别检测chan1和chan2是否可读,这个select语句不包含default语句。
参考答案:select会按照随机的顺序检测各case语句中channel是否ready,如果某个case中的channel已经ready则执行相应的case语句然后退出select流程,如果所有的channel都未ready且没有default的话,则会阻塞等待各个channel。所以上述程序会一直阻塞。
实例三:select语句中读操作要判断是否成功读取,关闭的channel也可以读取
package main
import (
"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 ready.")
case <-chan2:
fmt.Println("chan2 ready.")
}
fmt.Println("main exit.")
}
实例四:
package main
func main() {
select {
}
}
上面程序中只有一个空的select语句。
参考答案:对于空的select语句,程序会被阻塞,准确的说是当前协程被阻塞,同时Golang自带死锁检测机制,当发现当前协程再也没有机会被唤醒时,则会panic。所以上述程序会panic。
select实现原理
Golang实现select时,定义了一个数据结构表示每个case语句(含defaut,default实际上是一种特殊的case),select执行过程可以类比成一个函数,函数输入case数组,输出选中的case,然后程序流程转到选中的case块。
case数据结构
源码包src/runtime/select.go:scase定义了表示case语句的数据结构:
type scase struct {
c *hchan // chan
kind uint16
elem unsafe.Pointer // data element
}
scase.c为当前case语句所操作的channel指针,这也说明了一个case语句只能操作一个channel。
scase.kind表示该case的类型,分为读channel、写channel和default,三种类型分别由常量定义:
- caseRecv:case语句中尝试读取scase.c中的数据;
- caseSend:case语句中尝试向scase.c中写入数据;
- caseDefault: default语句
scase.elem表示缓冲区地址,跟据scase.kind不同,有不同的用途:
- scase.kind == caseRecv : scase.elem表示读出channel的数据存放地址;
- scase.kind == caseSend : scase.elem表示将要写入channel的数据存放地址;
select实现逻辑
源码包src/runtime/select.go:selectgo()定义了select选择case的函数:
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool)
函数参数:
- cas0为scase数组的首地址,selectgo()就是从这些scase中找出一个返回。
- order0为一个两倍cas0数组长度的buffer,保存scase随机序列pollorder和scase中channel地址序列lockorder
- pollorder:每次selectgo执行都会把scase序列打乱,以达到随机检测case的目的。
- lockorder:所有case语句中channel序列,以达到去重防止对channel加锁时重复加锁的目的。
- ncases表示scase数组的长度
函数返回值
- int: 选中case的编号,这个case编号跟代码一致
- bool: 是否成功从channle中读取了数据,如果选中的case是从channel中读数据,则该返回值表示是否读取成功。
selectgo实现伪代码如下:
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
//1. 锁定scase语句中所有的channel
//2. 按照随机顺序检测scase中的channel是否ready
// 2.1 如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index, true)
// 2.2 如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index, false)
// 2.3 所有case都未ready,则解锁所有的channel,然后返回(default index, false)
//3. 所有case都未ready,且没有default语句
// 3.1 将当前协程加入到所有channel的等待队列
// 3.2 当将协程转入阻塞,等待被唤醒
//4. 唤醒后返回channel对应的case index
// 4.1 如果是读操作,解锁所有的channel,然后返回(case index, true)
// 4.2 如果是写操作,解锁所有的channel,然后返回(case index, false)
}
特别说明:对于读channel的case来说,如case elem, ok := <-chan1:, 如果channel有可能被其他协程关闭的情况下,一定要检测读取是否成功,因为close的channel也有可能返回,此时ok == false。
参考文献:
https://my.oschina.net/renhc/blog/2253937/nhttps://blog.csdn.net/xdrbt/article/details/80287959
https://blog.csdn.net/wangxindong11/article/details/78591308
https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-select/
相关推荐
- sharding-jdbc实现`分库分表`与`读写分离`
-
一、前言本文将基于以下环境整合...
- 三分钟了解mysql中主键、外键、非空、唯一、默认约束是什么
-
在数据库中,数据表是数据库中最重要、最基本的操作对象,是数据存储的基本单位。数据表被定义为列的集合,数据在表中是按照行和列的格式来存储的。每一行代表一条唯一的记录,每一列代表记录中的一个域。...
- MySQL8行级锁_mysql如何加行级锁
-
MySQL8行级锁版本:8.0.34基本概念...
- mysql使用小技巧_mysql使用入门
-
1、MySQL中有许多很实用的函数,好好利用它们可以省去很多时间:group_concat()将取到的值用逗号连接,可以这么用:selectgroup_concat(distinctid)fr...
- MySQL/MariaDB中如何支持全部的Unicode?
-
永远不要在MySQL中使用utf8,并且始终使用utf8mb4。utf8mb4介绍MySQL/MariaDB中,utf8字符集并不是对Unicode的真正实现,即不是真正的UTF-8编码,因...
- 聊聊 MySQL Server 可执行注释,你懂了吗?
-
前言MySQLServer当前支持如下3种注释风格:...
- MySQL系列-源码编译安装(v5.7.34)
-
一、系统环境要求...
- MySQL的锁就锁住我啦!与腾讯大佬的技术交谈,是我小看它了
-
对酒当歌,人生几何!朝朝暮暮,唯有己脱。苦苦寻觅找工作之间,殊不知今日之事乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一...
- MySQL字符问题_mysql中字符串的位置
-
中文写入乱码问题:我输入的中文编码是urf8的,建的库是urf8的,但是插入mysql总是乱码,一堆"???????????????????????"我用的是ibatis,终于找到原因了,我是这么解决...
- 深圳尚学堂:mysql基本sql语句大全(三)
-
数据开发-经典1.按姓氏笔画排序:Select*FromTableNameOrderByCustomerNameCollateChinese_PRC_Stroke_ci_as//从少...
- MySQL进行行级锁的?一会next-key锁,一会间隙锁,一会记录锁?
-
大家好,是不是很多人都对MySQL加行级锁的规则搞的迷迷糊糊,一会是next-key锁,一会是间隙锁,一会又是记录锁。坦白说,确实还挺复杂的,但是好在我找点了点规律,也知道如何如何用命令分析加...
- 一文讲清怎么利用Python Django实现Excel数据表的导入导出功能
-
摘要:Python作为一门简单易学且功能强大的编程语言,广受程序员、数据分析师和AI工程师的青睐。本文系统讲解了如何使用Python的Django框架结合openpyxl库实现Excel...
- 用DataX实现两个MySQL实例间的数据同步
-
DataXDataX使用Java实现。如果可以实现数据库实例之间准实时的...
- MySQL数据库知识_mysql数据库基础知识
-
MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...
- 如何为MySQL中的JSON字段设置索引
-
背景MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)