GO学习之 goroutine的调度原理

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)
7、GO学习之 多线程(goroutine)
8、GO学习之 函数(Function)
9、GO学习之 接口(Interface)
10、GO学习之 网络通信(Net/Http)
11、GO学习之 微框架(Gin)
12、GO学习之 数据库(mysql)
13、GO学习之 数据库(Redis)
14、GO学习之 搜索引擎(ElasticSearch)
15、GO学习之 消息队列(Kafka)
16、GO学习之 远程过程调用(RPC)
17、GO学习之 goroutine的调度原理

文章目录

  • GO系列
  • 前言
  • 一、goroutine 调度器
  • 二、goroutine调度模型与演进过程
    • 2.1 G-M 模型
    • 2.2 G-P-M 模型
    • 2.3 抢占式调度
    • 2.4 NUMA调度模型
  • 三、goroutine 调度器原理
  • 四、调度器状态查看
  • 五、小结

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
并发与并行:21世纪以后,数据中心的硬件和网络环境发生了重大变化,多核处理器硬件成为数据中心的主流。而20世纪的主流编程语言(C++,java等)并非以解决多核和网络环境下日益复杂的问题而生,即便这些语言在后续的版本中努力做了有针对性的改善,但毕竟积重难返,其最初的语音设计决定了开发人员想要有效地利用多核环境的强大计算能力,要付出的心智负担依旧很好。

  • 并行方案:就是在处理核数充足的情况下启动多个单线程应用的实例
  • 并发方案:并发就要从小做应用结构设计,即将应用分解成多个在基本单元中执行,可能有一定关联关系的代码片段。

Go 语言的设计哲学之一是 “原生并发,轻量高效”。Go 并未使用操作系统线程作为分解后的代码片段的基本执行单元,而是实现了 goroutine 这一由Go运行时负责调度的用户层轻量级线程为并发程序设计提供原生支持。
goroutine相比传统操作系统线程而言具有如下优势:

  1. 资源占用小,每个 goroutine 的初始栈大小仅为 2KB
  2. 由 Go 运行时而不是操作系统调度,goroutine 上下文切换代价小。
  3. 语言原生支持:goroutine 由 Go 关键字函数或方法创建,函数或方法返回即表示 goroutine 退出,开发体验更佳。
  4. 内置 channel 作为 goroutine 间通信原语,并并发设计提供强大支撑。

—— 以上内容摘自《Go语音精进之路》一书。
Go语言精进之路
此篇内容也是借鉴此书而写。

一、goroutine 调度器

什么是 goroutine调度器呢?提到 “调度”,想到的就是有一个中央控制器,对资源的各种调度以达到某种目的,比如操作系统对进程、线程的调度。操作系统调度器会将操作系统中的多个线程按照一定的算法调度到物理CPU上运行。传统的编程语言的并发实现多试基于线程模型的,即应用程序负责创建线程,操作系统负责调度线程。然而这种传统支持并发的方式有诸多不足,比如:使用复杂、线程安全、难以扩展等。
为此,Go 采用用户层轻量级线程来解决这些问题,并称之为 goroutine
由于 goroutine 占用资源很少,一个 Go 程序中可以创建上万个并发的 goroutine。而将这些 goroutine 按照一定的算法放到 CPU 上执行的程序就称之为 goroutine 调度器
一个 Go 程序对于操作系统来说只是一个用户层程序,操作系统只有线程, goroutine 的调度全要靠 Go 自己完成。

二、goroutine调度模型与演进过程

2.1 G-M 模型

Go 1.0正式版本中,Go 开发团队实现了一个简单的 goroutine 调度器。这个调度器中,每个 goroutine 对应与运行中的一个抽象结构 —— G(goroutine),而被视作 “物理CPU” 的操作系统线程则被抽象为另一个结构 —— M(machine)。这个模型比较简单且能正常工作,但存在着诸多问题。

原文如下:
前英特尔黑带级工程师、现谷歌工程师Dmitry Vyukov在“Scalable Go Scheduler Design”一文中指出了G-M模型的一个重要不足:限制了Go并发程序的伸缩性,尤其是对那些有高吞吐或并行计算需求的服务程序
问题主要体现在如下几个方面。

  • 单一全局互斥锁(Sched.Lock)和集中状态存储的存在导致所有goroutine相关操作(如创建、重新调度等)都要上锁。
  • goroutine传递问题:经常在M之间传递“可运行”的 goroutine 会导致调度延迟增大,带来额外的性能损耗。每个M都做内存缓存,导致内存占用过高,数据局部性较差。

因系统调用(syscall)而形成的频繁的工作线程阻塞和解除阻塞会带来额外的性能损耗。

2.2 G-P-M 模型

在发现了 G-M 模型的不足后,这位大佬(Dmitry Vyukov)改进了 goroutine 调度器,实现了 G-P-M 调度模型 和 work stealing 算法,如图所示:
G-P-M调度模型
这位大佬向 G-M 模型中增加了一个 P,使得 goroutine 调度器具有很好的伸缩性。
那 P 是什么?P 是一个 “逻辑处理器”,每个 G 想要真正运行起来,首先需要分配一个 P,即进入P 的 本地运行队列(local runq)中。对于 G 来说,P 就是运行它的 “CPU”,可以说在 G的眼里只有 P。但从 goroutine 调度器的视角来看,真正的 “CPU” 是 M。只有将 P 和 M 绑定才能让 P 的本地运行队列中的 G 真正运行。这样一来 P 与 M 的对应关系是 多对多(N:M)。

2.3 抢占式调度

在实现了 G-P-M 调度模型这一大进步之后,调度器仍然有一个头疼的问题,那就是不支持抢占式调度,这导致一旦某个 G 中出现死循环的代码逻辑,那么 G 将永久占用分配给它的 P 和 M,而位于同一个 P 中的其他 G 将得不到调度,出现 “饿死” 情况。
于是大佬又提出了 “抢占式调度” 设计, 并在 Go 1.2 版本中实现了抢占式调度。

这个抢占式调度的原理是在每个函数或方法的入口加上一段额外的代码,让运行时有机会检查是否需要执行抢占调度。这种协作式抢占调度的解决方案只是局部解决了“饿死”问题,对于没有函数调用而是纯算法循环计算的G,goroutine调度器依然无法抢占

2.4 NUMA调度模型

在 Go 1.2 以后,Go 重点放在了 GC 低延迟的优化上,大佬虽然提出了 NUMA 调度模型,但没有真正落地实现,还有待考察。

三、goroutine 调度器原理

  1. G P M 介绍

G:代表goroutine,存储了goroutine的执行栈信息、goroutine状态及goroutine的任务函数等。另外G对象是可以重用的。
P:代表逻辑processor,P的数量决定了系统内最大可并行的G的数量(前提:系统的物理CPU核数>=P的数量)。P中最有用的是其拥有的各种G对象队列、链表、一些缓存和状态。
M:M代表着真正的执行计算资源。在绑定有效的P后,进入一个调度循环;而调度循环的机制大致是从各种队列、P的本地运行队列中获取G,切换到G的执行栈上并执行G的函数,调用goexit做清理工作并回到M。如此反复。M并不保留G状态,这是G可以跨M调度的基础。

  1. G 被抢占调度
    与操作系统按时间片调度线程不同,Go 中并没有时间片概念。如果某个 G 没有进行系统调用(syscll)、没有进行 I/O 操作,也没有阻塞在一个 channel 操作上,那么 M 是如何让 G 停下来并调度下一个可运行的G 呢?
    答案是:G 是被抢占调度的

  2. channel阻塞 或者 网络I/O 情况下的调度
    如果 G 被阻塞在某个 channel操作 或者 网络I/O 操作上,那么 G 会被放置在某个等待队列中,而 M 会尝试运行 P 的下一个运行的 G。
    如果此时 P 没有可运行的 G 提供 M 运行,那么 M 将解绑 P,并进入挂起状态。
    当 I/O 操作 或者 channel操作完成,在等待队列中的 G 会被唤醒,标记为 runnable 状态,并进图某个 P 的队列中,绑定一个 M 后继续执行。

  3. 系统调用阻塞情况下的调度
    如果 G 被阻塞在某个系统调用上,那么不仅 G 会阻塞,执行该 G 的 M 也会解绑 P,与 G 一起进入阻塞状态;如果此时有空闲的 M,则 P 会与其绑定并继续执行其他 G;如果没有空闲的 M,但任然有其他 G 要执行,那么就会创建一个新 M;
    当系统调用返回后,阻塞在该系统调用上的 G 会尝试获取一个可用的 P,如果有可用 P,之前运行该 G 的 M 将绑定 P 继续运行 G。如果没有可用的 P,那么 G 与 M 之间的关系将解绑,同时 G 会被标记为 runnable,放入全局的运行队列中,带的调度器的再次调度。

四、调度器状态查看

Go提供了调度器当前状态的查看方法:使用Go运行时环境变量GODEBUG
关于Go调度器调试信息输出的详细信息,可以参考Dmitry Vyukov的文章 Debugging Performance Issues in Go Programs,这也应该是每个Gopher必读的经典文章。
更详尽的信息可参考$GOROOT/src/runtime/proc.go中schedtrace函数的实现。

五、小结

goroutine是Go语言并发的基础,也是最基本的执行单元。Go基于goroutine建立了G-P-M的调度模型,了解这个调度模型对于Go代码设计以及Go代码问题的诊断都有很大帮助。

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

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

相关文章

JavaSE入门---掌握面向对象三大特性:封装、继承和多态

文章目录 封装什么是封装?如何实现封装? 继承什么是继承?继承的语法父类成员访问子类访问父类的成员变量子类访问父类的成员方法 认识super关键字认识final关键字子类的构造方法super VS this在继承关系中各代码块的执行顺序是怎样的&#xf…

数据可视化与GraphQL:利用Apollo创建仪表盘

前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 「推荐专栏」: ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄,vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

YOLO目标检测——人脸识别数据集【对应voc、coco和yolo三种格式标签】

实际项目应用:安全监控、智能驾驶、人机交互、人脸门禁、人脸支付、人脸搜索数据集说明:人脸识别数据集,真实场景的高质量图片数据,数据场景丰富,含有人脸图片标签说明:使用lableimg标注软件标注&#xff0…

OSG嵌入QT的简明总结2

正文 我之前在这篇博文《OSG嵌入QT的简明总结》中论述了OSG在QT中显示的可视化问题。其中提到官方提供的osgQt项目(地址:https://github.com/openscenegraph/osgQt )很久前已经更新了。但是我一直没有时间同步更新,最近重新尝试了…

PHP 基础

PHP 基础 概述 在PHP 文件中&#xff0c;可以与HTML 和JavaScript 混编。 开始标记<?php 表示进入PHP 模式&#xff0c;结束标记?>&#xff0c;标识退出PHP 模式。 PHP 模式之外的内容会被作为字符输出到浏览器中。 PHP 在服务端执行&#xff0c;HTML 和 JS 在浏览…

vim的使用以及配置

vim的使用以及配置 vim的介绍vim的使用vim的三种模式vim的三种模式的转换方式三个模式的运用命令模式插入模式底行模式 vim的配置vim一键配置命令 vim的介绍 百度官方介绍&#xff1a; Vim是一个类似于Vi的著名的功能强大、高度可定制的文本编辑器&#xff0c;在Vi的基础上改进…

【iOS】UITableView总结(Cell的复用原理、自定义Cell、UITableViewCell协议方法)

UITableView 列表的特点&#xff1a; 数据量大样式较为统一通常需要分组垂直滚动通常可视区只有一个 -> 视图的复用 UITableViewDataSource UITableView作为视图&#xff0c;只负责展示&#xff0c;协助管理&#xff0c;不管理数据 需要开发者为UITableView提供展示所需…

登上抖音热搜榜:如何让你的内容火爆全网

在当今信息爆炸的时代&#xff0c;抖音已经成为了很多人获取信息、娱乐和社交的重要平台。每一天&#xff0c;都有大量的短视频在抖音上诞生&#xff0c;然而&#xff0c;只有少数幸运儿能够登上抖音热搜榜&#xff0c;成为万人瞩目的焦点。那么&#xff0c;如何让你的内容火爆…

Go并发编程之四

一、前言 今天我们介绍一下Go并发编程另外一个重要概念【多路复用】&#xff0c;多路复用最开始是在网络通讯领域&#xff08;硬件&#xff09;应用&#xff0c;指的是用同一条线路承载多路信号进行通信的方式&#xff0c;有频分多路复用、时分多路复用等等技术&#xff0c;然…

MySQL日志 错误日志 二进制日志 查询日志 慢查询日志

一、错误日志 错误日志是MySQL中最重要的日志之一,它记录了当mysqld启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。 该日志是默认开启的&#xff0c;默认存放目录/var/log/,默认的日志文件名…

小程序路由传参的方法?

小程序路由传参的方法有两种&#xff1a; 1.通过URL参数传递&#xff1a;可以在跳转页面时在URL中携带参数&#xff0c;目标页面可以通过获取URL参数来使用。使用wx.navigateTo或wx.redirectTo方法进行页面跳转&#xff0c;并在URL中添加参数。 示例代码如下&#xff1a; // …

2019年亚太杯APMCM数学建模大赛A题基于图像分析的二氧化硅熔化表示模型求解全过程文档及程序

2019年亚太杯APMCM数学建模大赛 A题 基于图像分析的二氧化硅熔化表示模型 原题再现 铁尾矿的主要成分是二氧化硅&#xff0c;而二氧化硅是铁尾矿成分中最难熔化的部分。因此&#xff0c;铁尾矿的熔融行为可以用二氧化硅的熔融行为来表示。然而&#xff0c;高温熔池的温度超过…

Android FloatingActionButton 使用神坑记录

1.FloatingActionButton 死活都不是圆形 必须加上下面这个属性才是圆形 app:shapeAppearance"style/ShapeAppearanceOverlay.Material3.Button"2.FloatingActionButton 中间的icon不居中 加上下面的属性&#xff0c;然后大小跟你的控件大小一致 app:fabCustomSiz…

C语言标准规范版本介绍

0 Preface/Foreword ANSI&#xff1a;American National Standards Institute&#xff0c;美国国家标准协会 ISO&#xff1a;International Organization for Standardization&#xff0c;国际标准组织 IEC&#xff1a;International Electrotechnical Commission&#xff0…

Jmeter的性能测试

性能测试的概念 定义&#xff1a;软件的性能是软件的一种非功能特性&#xff0c;它关注的不是软件是否能够完成特定的功能&#xff0c;而是在完成该功能时展示出来的及时性。 由定义可知性能关注的是软件的非功能特性&#xff0c;所以一般来说性能测试介入的时机是在功能测试…

Python---练习:while循环嵌套(用两次while三步走--里外各一次)

1、循环嵌套的引入 案例&#xff1a; 有天女朋友又生气了&#xff0c;惩罚&#xff1a;说3遍“老婆大人&#xff0c; 我错了”&#xff0c;这个程序是不是循环即可&#xff1f;但如果女朋友说&#xff1a;还要刷今天晚饭的碗&#xff0c;这个程序怎么书写&#xff1f; 思考&…

面试题 01.06. 字符串压缩

​​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;面试题 01.06. 字符串压缩 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 计算压缩后的字符串长度&#xff0c;如果该长度小于原字符串长度&#xff0c;返回压缩后的字符串&#xff0c;否则…

MOSFET(五):DrMos

一、简介 DrMos&#xff08;Driver MOSFET&#xff09;技术是 Intel 于2004年推出的服务器主板节能技术&#xff0c;即把 2 个MOSFET和 1 个MOS驱动器 三合一&#xff0c;集成在一个封装中。集成后的 DrMos 面积是分离MOSFET的 &#xff0c;功率密度是其 倍&#xff0c;通过搭…

Flink中的时间和窗口操作

1.窗口概念 在大多数场景下,我们需要统计的数据流都是无界的,因此我们无法等待整个数据流终止后才进行统计。通常情况下,我们只需要对某个时间范围或者数量范围内的数据进行统计分析:如每隔五分钟统计一次过去一小时内所有商品的点击量;或者每发生1000次点击后,都去统计一…

android 13/14高版本SurfaceFlinger出现VSYNC-app/VSYNC-appSf/VSYNC-sf剖析

问题背景&#xff1a; 了解surfaceflinger的vsync同学都可能知道vsync属于一个节拍器&#xff0c;主要用来控制有节奏的渲染&#xff0c;不至于会产生什么画面撕裂等现象。 一般vsync都有会有2部分&#xff1a; app部分vsync&#xff0c;控制各个app可以有节奏的上帧 surfacef…