深入理解 Golang: Goroutine 协程

这篇具有很好参考价值的文章主要介绍了深入理解 Golang: Goroutine 协程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

进程用来分配内存空间,是操作系统分配资源的最小单位;线程用来分配 CPU 时间,多个线程共享内存空间,是操作系统或 CPU 调度的最小单位;协程用来精细利用线程。协程就是将一段程序的运行状态打包,可以在线程之间调度。或者说将一段生产流程打包,使流程不固定在生产线上。协程不是被操作系统内核所管理,而完全是由程序所控制。

Go 中协程的本质

协程在 Go 内部的表示如下:

type g struct {
 stack        stack   // 协程栈
 sched        gobuf   // 目前程序运行现场
 atomicstatus atomic.Uint32 // 协程状态
 goid         uint64 // 协程 id
 
 // 。。。省略一些其他属性
 
}

type stack struct {
 lo uintptr
 hi uintptr
}

type gobuf struct {
 sp   uintptr // 栈指针,指向当前协程运行到哪个地方
 pc   uintptr // 程序计数器,记录运行到了哪行代码
 g    guintptr
 ctxt unsafe.Pointer
 ret  uintptr
 lr   uintptr
 bp   uintptr // for framepointer-enabled architectures
}

深入理解 Golang: Goroutine 协程

而线程的描述为一个 m 结构体:

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

 mOS
  // 。。。省略一些其他属性
}
  • runtime 中将操作系统线程抽象为 m 结构体
  • g0 协程,操作调度器
  • curg 记录当前运行的协程
  • mOS 记录操作系统线程信息

单线程循环 Go 0.x
首先进入 g0 stack,执行 schedule() 方法,在 schedule 方法中调用 execute() 方法,execute 中再调用 gogo() 方法,gogo 方法为汇编语言编写,针对不同平台提供不同处理方法,接着通过 gogo 方法,从全局队列 runnable queue 中获取任务协程 g,进入到用户自定义的业务方法中。此时使用业务协程 g 自己的栈记录调用、跳转关系、本地变量等信息。

业务逻辑方法执行完成后会回退到 goexit() 的栈帧,goexit 会进行栈的切换,切换到 g0 stack,继续执行 schedule 方法链,不停地将 runnable queue 中的业务方法协程 g 取出执行。

多线程循环 Go 1.0
在单线程的基础上,多个线程的标准调度循环同时从 runnable queue 中取出协程 g 执行。注意,为保证协程安全,runnable queue 需要加锁。

存在的问题

  1. 协程串行执行,无法并发,会有阻塞现象。
  2. 多线程并发时,会抢夺全局队列的全局锁。

G-M-P 调度模型

前面说到,多线程从全局队列中取协程出来执行时,需要对这个队列加锁,如果每个线程 m 每次获取锁后,只从中取一个 g,则会造成很大开销,存在极大的性能问题。一个朴素的思想就是,每次取多个,将这些协程维护在一个自己的本地队列 p 中,本地队列中的全部 g 执行完后,再去全局队列抓取一堆。
深入理解 Golang: Goroutine 协程
这个本地队列 p 在 Go 中的表示如下:

type p struct {
 // 指向服务的线程
 m           muintptr   // back-link to associated m (nil if idle)
 // Queue of runnable goroutines. Accessed without lock.
 runqhead uint32
 runqtail uint32
 // 队列,存放 g
 runq     [256]guintptr
 // 下一个可用协程指针
 runnext guintptr
 // 。。。省略其他属性
}
  • P 作为 M 与 G 的中介,承担’送料’的作用。
  • P 持有一些 G,使得每次获取 G 不用去全局找。
  • P 大大减少了并发冲突状况。

新建协程
创建一个新协程时,会随机查找一个 P,将该协程放到 P 的 runnext(优先执行),如果本地队列已经满了,则将新协程放到全局队列。

协程阻塞-触发切换
P 中本地队列里存在长耗时任务时,会阻塞后续协程,造成饥饿现象,一种做法是内部调用 runtime.gopark() 方法,让当前大任务进入等待状态,回到 execute() 继续执行后续操作。另外也可以完成系统调时后挂起或主动挂起,这里的主动的含义是用户自己的方法去触发 runtime.gopark()。
深入理解 Golang: Goroutine 协程
在极端情况下,本地队列中的协程全部为耗时任务,则会造成全局队列 runnable queue 饥饿问题,那么此时需要同时调度本地队列和全局队列:
深入理解 Golang: Goroutine 协程
runtime 中的具体做法是:

func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
 // 。。。
 
 // Check the global runnable queue once in a while to ensure fairness.
 // Otherwise two goroutines can completely occupy the local runqueue
 // by constantly respawning each other.
 if pp.schedtick%61 == 0 && sched.runqsize > 0 {
  lock(&sched.lock)
  gp := globrunqget(pp, 1)
  unlock(&sched.lock)
  if gp != nil {
   return gp, false, false
  }
 }
 
 // 。。。
}

执行 61 次线程循环后,gp := globrunqget(pp, 1) 去全局协程队列里拿 1 个协程进本地队列。文章来源地址https://www.toymoban.com/news/detail-511115.html

到了这里,关于深入理解 Golang: Goroutine 协程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Go语言入门12(协程 goroutine)

    进程 ​当运行一个应用程序的时候,操作系统会为这个应用程序启动一个进程。可以将这个进程看作一个包含了应用程序在运行中需要用到和维护的各种资源的容器。这些资源包括但不限于内存地址空间、文件和设备的句柄以及线程 线程 ​一个线程是一个执行空间,这个空

    2023年04月26日
    浏览(39)
  • 深入理解Java虚拟机——内存分配与回收策略

    在读这篇博客之前,你需要了解分代收集理论中,收集器应该将Java堆划分出不同的区域**,**然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。 例如 appel式回收 ,HotSpot虚拟机中的新生代收集器都采用了appel式回收来设计新生代内

    2024年02月04日
    浏览(42)
  • 《深入理解Java虚拟机》读书笔记:内存分配策略

    Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存。关于回收内存这一点,我们已经使用了大量篇幅去介绍虚拟机中的垃圾收集器体系以及运作原理,现在我们再一起来探讨一下给对象分配内存的那点事

    2024年02月13日
    浏览(55)
  • 深入理解 slab cache 内存分配全链路实现

    本文源码部分基于内核 5.4 版本讨论 在经过上篇文章 《从内核源码看 slab 内存池的创建初始化流程》 的介绍之后,我们最终得到下面这幅 slab cache 的完整架构图: 本文笔者将带大家继续从内核源码的角度继续拆解 slab cache 的实现细节,接下来笔者会基于上面这幅 slab cache 完

    2024年02月02日
    浏览(45)
  • Go学习第十一章——协程goroutine与管道channel

    1 协程goroutine 1.1 基本介绍 前置知识:“进程和线程”,“并发与并行” 协程的概念 协程(Coroutine)是一种用户态的轻量级线程,不同于操作系统线程,协程能够在单个线程中实现多任务并发,使用更少的系统资源。协程的运行由程序控制,不需要操作系统介入,因此协程之

    2024年02月08日
    浏览(40)
  • 深入理解JVM——垃圾回收与内存分配机制详细讲解

    所谓垃圾回收,也就是要回收已经“死了”的对象。 那我们如何判断哪些对象“存活”,哪些已经“死去”呢? 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加一;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能再被使用的。 但是

    2024年02月12日
    浏览(44)
  • 《深入理解Java虚拟机》读书笔记:内存分配与回收策略

    Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存。关于回收内存这一点,我们已经使用了大量篇幅去介绍虚拟机中的垃圾收集器体系以及运作原理,现在我们再一起来探讨一下给对象分配内存的那点事

    2024年02月13日
    浏览(40)
  • 深入理解 Golang: 网络编程

    关于计算机网络分层与 TCP 通信过程过程此处不再赘述。 考虑到 TCP 通信过程中各种复杂操作,包括三次握手,四次挥手等,多数操作系统都提供了 Socket 作为 TCP 网络连接的抽象。 Linux - Internet domain socket - SOCK_STREAM Linux 中 Socket 以 “文件描述符” FD 作为标识 在进行 Socket 通

    2024年02月11日
    浏览(40)
  • 【云原生-深入理解 Kubernetes 系列 3】深入理解容器进程的文件系统

    【云原生-深入理解Kubernetes-1】容器的本质是进程 【云原生-深入理解Kubernetes-2】容器 Linux Cgroups 限制 大家好,我是秋意零。 😈 CSDN作者主页 😎 博客主页 👿 简介 👻 普通本科生在读 在校期间参与众多计算机相关比赛,如:🌟 “省赛”、“国赛” ,斩获多项奖项荣誉证书

    2024年02月06日
    浏览(49)
  • 深入理解Golang中的接口与实例展示

    标题:深入理解Golang中的接口与实例展示 引言: Golang(Go)的接口是一项强大的特性,它为面向对象编程带来了灵活性和可维护性。本文将深入讲解Golang中的接口概念,从基础到实际应用,通过详细案例展示,帮助读者更好地掌握接口的使用和设计。 一、接口基础概念: 接

    2024年01月21日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包