详解go语言是如何实现协程的

 更新时间:2024年04月19日 09:36:13   作者:shark_chili  
go语言的精华就在于协程的设计,只有理解协程的设计思想和工作机制,才能确保我们能够完全的利用协程编写强大的并发程序,所以本文将给大家介绍了go语言是如何实现协程的,文中有详细的代码讲解,需要的朋友可以参考下

写在文章开头

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

详解协程工作机制和实现

协程示例

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

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语言的协程结构为:

  • 通过一个stack记录其高地址和低地址。
  • 通过schedsp(即stackpointer)栈帧的指针程序计数器pc(指向下一条运行的指令).
  • 采用goid生成唯一标识。
  • 然后再用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开始工作:

  • 通过execute从协程队列中获取任务。
  • 调用gogo方法在协程调用前插入go exit指针它记录g0栈帧,这个指针就是用于协程执行退出或者挂起是可以通过这个指针跳回g0栈。
  • 然后就是执行当前协程。
  • 协程执行完成切换回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语言底层实现的角度完整的剖析的协程与线程的关系和实现,希望对你有帮助。

以上就是详解go语言是如何实现协程的的详细内容,更多关于go实现协程的资料请关注脚本之家其它相关文章!

相关文章

  • 使用Golang打印特定的日期时间的操作

    使用Golang打印特定的日期时间的操作

    这篇文章主要给大家详细介绍了如何使用Golang打印特定的日期时间的操作,文中有详细的代码示例,具有一定的参考价值,需要的朋友可以参考下
    2023-07-07
  • 一文带你掌握Golang基础之通道

    一文带你掌握Golang基础之通道

    在Java中,多线程之间的通信方式有哪些?记得吗?Java多线程间通信的解决方案有很多种,比如:synchronized。在go中,就一种:通道,文中介绍的非常详细,感兴趣的同学可以参考下
    2023-05-05
  • golang 中 recover()的使用方法

    golang 中 recover()的使用方法

    这篇文章主要介绍了Guam与golang  recover()的使用方法,Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,下文更多相关资料需要的小伙伴可以参考一下
    2022-04-04
  • golang语言中wasm 环境搭建的过程详解

    golang语言中wasm 环境搭建的过程详解

    将 golang 打包为 WASM,通常有两种打包方式,一种是 golang 自带的,另外是使用 tinygo ,接下来通过本文给大家介绍golang语言中wasm 环境搭建的过程,感兴趣的朋友一起看看吧
    2021-11-11
  • 基于Go语言简单实现事件管理器

    基于Go语言简单实现事件管理器

    在编程中,事件管理器是一种常见的工具,用于通过通知来触发操作,本文将介绍一个简单的Go事件管理器的实现,并通过异步改进提高其性能,感兴趣的可以了解下
    2023-11-11
  • Go语言中Timer计时器的使用技巧详解

    Go语言中Timer计时器的使用技巧详解

    Go语言中的time包里有个Timer计时器的功能,这篇文章主要就是来和大家介绍一下Timer计时器的使用技巧,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-07-07
  • go实现冒泡排序的示例代码

    go实现冒泡排序的示例代码

    这篇文章主要介绍了go实现冒泡排序的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • 利用Go语言搭建WebSocket服务端方法示例

    利用Go语言搭建WebSocket服务端方法示例

    这篇文章主要给大家介绍了利用Go语言搭建WebSocket服务端方法,文中通过示例代码介绍的非常详细,需要的朋友们可以参考借鉴,下面来一起看看吧。
    2017-04-04
  • Go语言面试题之select和channel的用法

    Go语言面试题之select和channel的用法

    金九银十面试季到了(PS:貌似今年一年都是面试季),就业环境很差,导致从业人员不得不卷。本文将重点讲解一下Go面试进阶知识点之select和channel,需要的可以参考一下
    2022-09-09
  • 浅谈beego默认处理静态文件性能低下的问题

    浅谈beego默认处理静态文件性能低下的问题

    下面小编就为大家带来一篇浅谈beego默认处理静态文件性能低下的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06

最新评论