扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • 面试宝典系列

    • Golang面试题汇总
  • 宝典内容

    • 1.Golang中除了加Mutex锁以外还有哪些方式安全读写共享变量
    • 2.无缓冲Chan的发送和接收是否同步?
    • 3.Golang并发机制以及它所使用的CSP并发模型
    • 4.Golang中常用的并发模型
    • 5.Go中对nil的Slice和空Slice的处理是一致的吗?
    • 6.协程和线程和进程的区别?
    • 7.Golang的内存模型中为什么小对象多了会造成GC压力?
    • 8.Go中数据竞争问题怎么解决?
    • 9.什么是channel,为什么它可以做到线程安全?
    • 10.Golang垃圾回收算法?
    • 11.GC的触发条件
    • 12.Go的GPM如何调度?
    • 13.并发编程概念是什么?
    • 14.Go语言的栈空间管理是怎么样的?
    • 15.Goroutine和Channel的作用分别是什么?
    • 16.怎么查看Goroutine的数量?
    • 17.Go中的锁有哪些?
    • 18.怎么限制Goroutine的数量?
    • 19.Channel是同步的还是异步的?
    • 20.Goroutine和线程的区别?
    • 21.Go的Struct能不能比较?
    • 22.Go的defer原理是什么?
    • 23.Go的select可以用于什么?
    • 24.Context包的用途是什么?
    • 25.Go主协程如何等其余协程完再操作?
    • 26.Go的Slice如何扩容?
    • 27.Go中的map如何实现顺序读取?
    • 28.Go中CAS是怎么回事?
    • 29.Go中的逃逸分析是什么?
    • 30.Go值接收者和指针接收者的区别?
    • 31.Go的对象在内存中是怎样分配的?
    • 32.栈的内存是怎么分配的?
    • 33.堆内存管理怎么分配的?
    • 34.Go中的defer函数使用下面的两种情况下结果是什么?
    • 35.在Go函数中为什么会发生内存泄露?
    • 36.Go中new和make的区别?
    • 37.G0的作用?
    • 38.Go中的锁如何实现?
    • 39.Go中的channel的实现?
    • 40.Go中的map的实现
    • 41.Go中的http包的实现原理?
    • 42.Goroutine发生了泄漏如何检测?
    • 43.Go函数返回局部变量的指针是否安全?
    • 44.Go中两个Nil可能不相等吗?
    • 45.Goroutine和KernelThread之间是什么关系?

扫码订阅《 》或入驻星球,即可阅读文章!

12.Go的GPM如何调度?


面试宝典
Go的GPM如何调度?

Goroutine协程:

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。 因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作执行者则是用户自身程序,goroutine也是协程。

groutine能拥有强大的并发实现是通过GPM调度模型实现.

Go的调度器内部有四个重要的结构:M,G,P,Sched,如上图所示(Sched未给出).

  • M: M代表内核级线程,一个M就是一个线程,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息.
  • G: 代表一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度.
  • P: P全称是Processor,逻辑处理器,它的主要用途就是用来执行goroutine的,所以它也维护了一个goroutine队列,里面存储了所有需要它来执行的goroutine.
  • Sched:代表调度器,它维护有存储M和G的队列以及调度器的一些状态信息等.

Go中的GPM调度:

新创建的Goroutine会先存放在Global全局队列中,等待Go调度器进行调度,随后Goroutine被分配给其中的一个逻辑处理器P,并放到这个逻辑处理器对应的Local本地运行队列中,最终等待被逻辑处理器P执行即可。 在M与P绑定后,M会不断从P的Local队列中无锁地取出G,并切换到G的堆栈执行,当P的Local队列中没有G时,再从Global队列中获取一个G,当Global队列中也没有待运行的G时,则尝试从其它的P窃取部分G来执行相当于P之间的负载均衡。

从上图中可以看到,有2个物理线程M,每一个M都拥有一个处理器P,每一个也都有一个正在运行的goroutine。P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。

图中灰色的那些goroutine并没有运行,而是出于ready的就绪态,正在等待被调度。P维护着这个队列(称之为runqueue),Go语言里,启动一个goroutine很容易:go function 就行,所以每有一个go语句被执行,runqueue队列就在其末尾加入一个goroutine,在下一个调度点,就从runqueue中取出(如何决定取哪个goroutine?)一个goroutine执行。

当一个OS线程M0陷入阻塞时,P转而在运行M1,图中的M1可能是正被创建,或者从线程缓存中取出。

当MO返回时,它必须尝试取得一个P来运行goroutine,一般情况下,它会从其他的OS线程那里拿一个P过来, 如果没有拿到的话,它就把goroutine放在一个global runqueue里,然后自己睡眠(放入线程缓存里)。所有的P也会周期性的检查global runqueue并运行其中的goroutine,否则global runqueue上的goroutine永远无法执行。

另一种情况是P所分配的任务G很快就执行完了(分配不均),这就导致了这个处理器P处于空闲的状态,但是此时其他的P还有任务,此时如果global runqueue没有任务G了,那么这个P就会从其他的P里偷取一些G来执行。

通常来说,如果P从其他的P那里要拿任务的话,一般就拿run queue的一半,这就确保了每个OS线程都能充分的使用。