前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 重要知识:golang 调度

golang 重要知识:golang 调度

原创
作者头像
lincoln
修改2021-07-30 10:27:54
9701
修改2021-07-30 10:27:54
举报
文章被收录于专栏:后端后端

摘要

Go 的调度机制相当于我们微服务里的基础组件。很多运行时操作都涉及到了调度的关联。本文会细聊调度概念,策略,以及它的机制。当然,也少不了最常提及的 GMP 模型。

一、调度是什么?

计算机的资源有限的,像 CPU,内存都是固定的。但是同一时间可能会有多个任务要去完成,比如操作系统的定时监控,用户程序的运行等。

怎么让资源最大化的完成任务,这是调度需要考虑的关键点。

调度可以理解为一个指挥员,指导我们的程序按照一定的规则去获取资源,然后去执行里面的指令。

调度分配
调度分配

那么,一般的规则有哪些呢?

常见的调度策略有 2 种,一种是协作式调度,会让程序顺利的完成自己的任务,再把资源腾出来给其他程序使用。

另一种是抢占式调度,也就是让程序按一定的时间去占有这些资源,时间到了就被迫让出现有资源,给其他的程序轮流使用。

协作式调度有利于程序专注的完成自己的任务,但也可能会造成其他程序一直饿死,得不到执行。

协作式调度
协作式调度

抢占式调度有利于程序在资源的利用上雨露均沾,但是在不断的切换过程中,将会使得程序原本 10 ms 能完成的事,不得不延迟多几 ms。

抢占式调度
抢占式调度

注:Linux 操作系统也是采用了抢占式调度,并且使用了 CFS:完全公平调度算法。通过对程序大致的运行时间来平衡调度,让越没有执行过的程序,越快被调度到。

当前大多数操作系统都是采用抢占式调度来执行程序的,毕竟很多操作系统都是面向用户,需要很高的响应速度,而且只要切换程序的周期够短,例如 50ms,那对于用户来讲,就像没切换一样。

二、golang 的调度

上面提及到抢占式调度会有个频繁切换的过程,在切换时,需要不断的保存或恢复上下文信息。

而这会涉及到操作系统内核态和用户态的切换,性能损耗会很大。

对此,golang 实现了属于自己的调度模型,采用了基于协作的抢占式调度。之所以是"协作"的,是因为 Go 的调度时机是由用户自己设置的,而这里的用户指的是 golang 的运行时 runtime

它会在下面的事件发生时进行调度触发:

使用关键字 go 垃圾回收 系统调用,如访问硬盘 同步阻塞调用,如 使用 mutex、channel

如果上面什么事件都没发生,则会有 sysmon 来监控 goroutine 的运行情况,对长时间运行的 goroutine 进行标记。一旦 goroutine 被标记了,那么它就会下次发生函数调用时,将自己挂起,再触发调度。

这里需要说明下的是,runtime 它相当于 Java 的虚拟机,负责了 Go 的很多东西,例如调度垃圾回收、内存管理等,可以说是涵盖了 Go 的基础引擎了。

更重要的是 runtime 是运行在用户态上的,相当于 Go 的调度是在用户态这一层进行的。

这样,每当 Go 有调度产生时,就不会伴随着用户态和内核态的切换,而是像前面提到过的策略那样去触发调度,这就降低了并发时的内核态与用户态的切换成本了。

三、golang 的 GPM 模型

为了实现 golang 的调度,golang 抽象出了三个结构,也就是我们常见的 G、P、M

G:也就是协程 goroutine,由 Go runtime 管理。我们可以认为它是用户级别的线程。

goroutine 非常的轻量,初始分配只有 2KB,当栈空间不够用时,会自动扩容。同时,自身存储了执行 stack 信息、goroutine 状态以及 goroutine 的任务函数等。

P:processor 处理器。P 的数量默认跟 CPU 的核心数一样,如果是多核的 CPU,则会有多个 P 会被创建。

每当有 goroutine 要创建时,会被添加到 P 上的 goroutine 本地队列上,如果 P 的本地队列已满,则会维护到全局队列里。

在进行调度时,会优先从本地队列获取 goroutine 来执行。

如果本地队列没有,会从其他的 P 上偷取 goroutine。

如果其他 P 上也没有,则会从全局队列上获取 goroutine。

这样通过上面的策略,就能尽最大努力保证有 goroutine 可运行

M:系统线程。在 M 上有调度函数,它是真正的调度执行者,M 需要跟 P 绑定,并且会让 P 按上面的原则挑出个 goroutine 来执行。

M 虽然从 P 上挑选了 G 执行,但 M 并不保存 G 的上下文信息,而是 G 自己保存了相关信息,这样有利于转移到其他 M 上,在不同的 M 上运行。

GPM模型
GPM模型

GPM 模型的优势点在于 G 包含了执行任务相关信息,M 提供了执行环境,并且有调度机制。而 P 则是他们两者的粘合剂。

假如没有 P 。那么 M 就会有争夺 G 的竞争问题,并且 M 的数量会不可控,会出现过多的 M 去处理 G。

一旦超过了 CPU 的核心数,那么就会将性能耗费在上下文切换过程中。

有了 P 这一层后,M 优先从 P 的本地队列获取 goroutine,减少并发竞争。并且保证了最多跟 CPU 核心数一样的 goroutine 数量在并行运行,充分利用了多核优势,又不被滥用。

总结

相信看过本文后,各位对 Golang 的调度有了一定的了解。正是因为基于协作的抢占式调度和 GMP 模型,Golang 的高并发高性能才有了底层保障。当然,大伙也可以深入到源码去分析这些调度机制,这样离大神就更近一步了 ㋡...


感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。

可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!

阅新技术,阅读更多的新知识。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 摘要
  • 一、调度是什么?
  • 二、golang 的调度
  • 三、golang 的 GPM 模型
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档