go语言是如何实现协程的

news2025/1/11 4:19:12

写在文章开头

go语言的精华就在于协程的设计,只有理解协程的设计思想和工作机制,才能确保我们能够完全的利用协程编写强大的并发程序。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

详解协程工作机制和实现

协程示例

正式介绍底层之前,我们给出一段协程的代码示例,可以看到笔者开启一个协程进行函数内部调用:

func foo1() {
	fmt.Println("foo1 调用 foo2")
	foo2()
}

func foo2() {
	fmt.Println("foo2调用foo3")
	foo3()
}

func foo3() {
	fmt.Println("foo3 执行了")
}

func main() {
	//设置WaitGroup等待协程结束
	var wg sync.WaitGroup
	wg.Add(1)

	go func() {
		foo1()
		defer wg.Done()
	}()

	//等待上述协程运行结束
	wg.Wait()
}

运行结果如下:

foo1 调用 foo2
foo2调用foo3
foo3 执行了

结合debug我们可以看到当前协程的调用栈帧,在函数调用前插入一个goexit的东西,结合这一点我们开始对协程的深入剖析:

在这里插入图片描述

协程实现结构

go语言的协程结构为:

  1. 通过一个stack记录其高地址和低地址。
  2. 通过sched sp(即stackpointer)栈帧的指针程序计数器pc(指向下一条运行的指令).
  3. 采用goid生成唯一标识。
  4. 然后再用atomicstatus记录其执行状态。

基于这几点我们结合上述的代码给出协程的底层结构,如下图所示,当前协程的stack记录整个foo1函数的高低地址,假设我们当前的协程go来到foo2函数准备调用foo3函数,我们的sched中的sp即stackpointer记录foo2的指针,同时因为foo2内部会调用foo3所以程序计数器pc记录着调用foo3的指令。

最后因为协程都是由线程调度的,所以协程的内部也有一个变量记录着当前线程的指针m:

在这里插入图片描述

到此我们了解了协程核心结构,同时我们也在runtime2.go这一文件中即给出上述所说的核心变量:

type g struct {
	//记录栈帧的高地址和低地址
	stack       stack   // offset known to runtime/cgo
	//......
	m         *m //执行当前协程的线程指针
	//记录当前堆栈的指针以及下一条指令的运行地址
	sched     gobuf
	atomicstatus atomic.Uint32
	goid         uint64
	
	//......
}

步入stack可以看到lohi两个专门记录栈帧高低地址的指针:

type stack struct {
	lo uintptr
	hi uintptr
}

对应的我们也给出sched 的类型gobuf,可以看到sppc两个核心指针变量:

type gobuf struct {
	
	sp   uintptr
	pc   uintptr
	//......
}

谈谈go语言对于线程的抽象

上文我们提出线程的用m指针记录,如下源码所示,我们都知道在go语言中每个线程都会从一个协程队列中获取协程执行,所以执行时它会用curg记录当前运行的协程,然后通过id对自己进行唯一标识,而mOS则是及记录当前操作系统信息,这其中最核心的就是g0它就是每一个线程的操作调度器:

type m struct {
	g0      *g     // goroutine with scheduling stack
	id            int64 
	
	curg          *g       // current running goroutine
	
	mOS

}

了解整体结构之后我们再来聊聊go语言线程的g0栈是如何工作的,如下图所示,每一个g0栈都会通过schedule开始工作:

  1. 通过execute从协程队列中获取任务。
  2. 调用gogo方法在协程调用前插入go exit指针它记录g0栈帧,这个指针就是用于协程执行退出或者挂起是可以通过这个指针跳回g0栈。
  3. 然后就是执行当前协程。
  4. 协程执行完成切换回g0栈,重新调用schedule方法再次从步骤1开始执行,由此构成一个循环。

在这里插入图片描述

这里我们也给出asm_amd64.s中关于gogo的汇编代码,可以看到gobuf_sp方法它会记录当前stack pointer也就是我们上文针对g0所说的g0栈地址:

TEXT gogo<>(SB), NOSPLIT, $0
	get_tls(CX)
	MOVQ	DX, g(CX)
	MOVQ	DX, R14		// set the g register
	//记录g0栈地址
	MOVQ	gobuf_sp(BX), SP	// restore SP
	MOVQ	gobuf_ret(BX), AX
	MOVQ	gobuf_ctxt(BX), DX
	MOVQ	gobuf_bp(BX), BP
	MOVQ	$0, gobuf_sp(BX)	// clear to help garbage collector
	MOVQ	$0, gobuf_ret(BX)
	MOVQ	$0, gobuf_ctxt(BX)
	MOVQ	$0, gobuf_bp(BX)
	MOVQ	gobuf_pc(BX), BX
	JMP	BX

小结

自此我们从go语言底层实现的角度完整的剖析的协程与线程的关系和实现,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

参考

程序计数器(PC)、堆栈指针(SP)与函数调用过程:https://www.cnblogs.com/uestcliming666/p/11488782.html

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

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

相关文章

51-M.2 B Key-5G模块 (U)SIM卡电路设计

视频链接 M.2 B Key-5G模块 &&#xff08;U&#xff09;SIM卡电路设计01_哔哩哔哩_bilibili M.2 B Key-5G模块 &&#xff08;U&#xff09;SIM卡电路设计 1、5G模块 &&#xff08;U&#xff09;SIM卡相关概念 1.1、5G模块&#xff08;RM500Q-GL&#xff09; R…

✯✯✯绍兴ISO9001认证:打造卓越质量管理的核心引擎✯✯✯

&#x1f308;绍兴ISO9001认证&#xff1a;&#x1f33a;打造卓越质量管理的&#x1f497;核心引擎&#x1f955; &#x1f688;在绍兴这座历史悠久、&#x1f345;文化底蕴深厚的城市中&#xff0c;&#x1f3e3;企业间的竞争日趋激烈。&#x1f481;‍♂️为了在这场激烈的&a…

LeetCode第797题: 所有可能的路径

目录 1.问题描述 2.问题分析 1.问题描述 给你一个有 n 个节点的有向无环图&#xff08;DAG&#xff09;&#xff0c;请你找出所有从节点 0 到节点 n-1 的路径并输出&#xff08;不要求按特定顺序&#xff09;。 graph[i] 是一个从节点 i 可以访问的所有节点的列表&#xff08…

openai api_key分享

sk-proj-aHU3aSlMAReiF8d6li9BT3BlbkFJsxmlRhLKlR55xIjpeJ10 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

二次元AI绘画生成器免费:教你生成精美图片

二次元AI绘画生成器&#xff0c;无疑是现代技术与艺术完美结合的典范。这些工具不仅将复杂的绘画过程简化&#xff0c;更让每一个艺术爱好者的创意得以充分展现。这些生成器能够精准捕捉大家的创意精髓&#xff0c;将其转化为细腻、独特的二次元画作。无论是角色设计、场景描绘…

波奇学Linux:ip协议

ip报文解析 4位版本&#xff1a;一般是4表示通信的ip版本号是ipv4还是ipv6 4位首部长度&#xff1a;数值*4ip报头长度 取值范围为[0101,1111], 报头长度就是[5*420,15*460] 8位服务类型(TOS)&#xff1a;4位TOS位段和3位优先权字段和一位保留字段 4位TOS相当于给路由器转发…

Redis快速入门操作

启动Redis 进入命令行客户端 字符串命令常用操作&#xff08;redis默认使用字符串来存储数据&#xff09; 列表&#xff08;Lists&#xff09;常用操作 集合&#xff08;Sets&#xff09;常用操作 &#xff08;无序集合且元素不可重复&#xff09; 有序集合&#xff08;So…

windows和虚拟机互传文件

在虚拟机中设置共享文件夹 操作方法&#xff1a;打开VMware–>虚拟机–>设置–>选项–>共享文件夹&#xff08;见下图&#xff09;&#xff0c;大家在共享文件夹当中就可以把Windows当中的D盘或者其它盘共享到虚拟机中。比如我就是将D盘和E盘共享到了虚拟机中。 共…

密码学 | 椭圆曲线密码学 ECC 入门(三)

目录 7 这一切意味着什么&#xff1f; 8 椭圆曲线密码学的应用 9 椭圆曲线密码学的缺点 10 展望未来 ⚠️ 原文地址&#xff1a;A (Relatively Easy To Understand) Primer on Elliptic Curve Cryptography ⚠️ 写在前面&#xff1a;本文属搬运博客&#xff0c;自己留…

pip下载包opencv出错(报错failed building wheel for opencv-python解决方法)

文章目录 1 报错2 原因3 解决方法参考 1 报错 ERROR: Could not build wheels for opencv-python, which is required to install pypr2 原因 版本不兼容的问题,当使用pip install opencv-python命令安装的是最新版本&#xff0c;当前python版本不支持。需要安装当前版本pyth…

「GO基础」在Windows上安装Go编译器并配置Golang开发环境

文章目录 1、安装Go语言编译程序1.1、下载GoLang编译器1.2、安装GoLang编译器 2、配置Golang IDE运行环境2.1、配置GO编译器2.1.1、GOROOT 概述2.1.2、GOROOT 作用2.1.2、配置 GOROOT 2.2、配置GO依赖管理2.2.1、Module管理依赖2.2.2、GOPATH 管理依赖 2.3、运行GO程序2.3.1、创…

Embedding例子:简单NN网络、迁移学习例子

一、简单例子&#xff1a;构造简单NN网络生成Embedding 1、pytorch例子 2、tensorflow例子 # 1导入模块 import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding import numpy as np# 2构建语料库 corpus[[…

配置静态IP【windows+ubuntu】

Windows配置静态IP 如下图所示&#xff0c;通过“网络和Internet进入设置界面”&#xff0c;依次操作“更改适配器选项”->选择要配置静态ip的网络“属性”->选择IPV4的属性->配置静态ip的地址、子网掩码、默认网关。默认网关应和路由器上的设置保持一致。 Ubuntu配…

2024红明谷杯——Misc 加密的流量

2024红明谷杯——Misc 加密的流量 写在前面&#xff1a; 这里是贝塔贝塔&#xff0c;照例来一段闲聊 打比赛但赛前一波三折&#xff0c;又是成功签到的一个比赛 说起来比赛全名叫红明谷卫星应用数据安全场景赛&#xff0c;但好像真的跟卫星的关系不大&#xff0c;没有bin方…

Redis中的订阅发布(三)

订阅发布 发送消息 当一个Redis客户端执行PUBLISH 命令将消息message发送给频道channel的时候&#xff0c;服务器需要执行以下 两个动作: 1.将消息message发送给channel频道的所有订阅者2.如果一个或多个模式pattern与频道channel相匹配&#xff0c;那么将消息message发送给…

基于SpringBoot+Vue的便利店管理系统 免费获取源码

项目源码获取方式放在文章末尾处 项目技术 数据库&#xff1a;Mysql5.7/8.0 数据表&#xff1a;11张 开发语言&#xff1a;Java(jdk1.8) 开发工具&#xff1a;idea 前端技术&#xff1a;vue 后端技术&#xff1a;SpringBoot 功能简介 (有文档) 项目获取关键字&#…

GUI02-在窗口上跟踪并输出鼠标位置(Win32版)

(1) 响应 WM_MOUSEMOVE 消息获得鼠标位置&#xff1b; (2) 响应 WM_PAINT 将鼠标位置输出到窗口中&#xff1b; (3) 学习二者之间的关键步骤&#xff1a;调用 InvalidateRect() 以通知窗口重绘。 零. 课堂视频 在窗口上跟踪输出鼠标位置-Win32版 一、关键知识点 1. BeginPaint…

Syncovery for Mac:高效文件备份和同步工具

Syncovery for Mac是一款专为Mac用户设计的文件备份和同步工具&#xff0c;凭借其高效、安全和易用的特点&#xff0c;深受用户好评。 Syncovery for Mac v10.14.2激活版下载 该软件具备强大的备份功能&#xff0c;支持多种备份方案和数据格式&#xff0c;用户可以根据需求轻松…

vscode自动生成返回值的快捷键

vscode中类似idea的altenter功能&#xff0c;可以添加返回值 idea中是Introduce local variable&#xff0c; vscode中按下command.(句号) 然后选extract to local variable或者 Assign statement to new local variable都行&#xff0c; 光标在分号前如图&#xff1a; 光标在…

维护SQLite的私有分支(二十六)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite、MySQL 和 PostgreSQL 数据库速度比较&#xff08;本文阐述时间很早比较&#xff0c;不具有最新参考性&#xff09;&#xff08;二十五&#xff09; 下一篇&#xff1a;SQLite数据库中JSON 函数和运算符 1…