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相比传统操作系统线程而言具有如下优势:
- 资源占用小,每个 goroutine 的初始栈大小仅为 2KB。
- 由 Go 运行时而不是操作系统调度,goroutine 上下文切换代价小。
- 语言原生支持:goroutine 由 Go 关键字函数或方法创建,函数或方法返回即表示 goroutine 退出,开发体验更佳。
- 内置 channel 作为 goroutine 间通信原语,并并发设计提供强大支撑。
—— 以上内容摘自《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-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 调度器原理
- 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调度的基础。
-
G 被抢占调度
与操作系统按时间片调度线程不同,Go 中并没有时间片概念。如果某个 G 没有进行系统调用(syscll)、没有进行 I/O 操作,也没有阻塞在一个 channel 操作上,那么 M 是如何让 G 停下来并调度下一个可运行的G 呢?
答案是:G 是被抢占调度的。 -
channel阻塞 或者 网络I/O 情况下的调度
如果 G 被阻塞在某个 channel操作 或者 网络I/O 操作上,那么 G 会被放置在某个等待队列中,而 M 会尝试运行 P 的下一个运行的 G。
如果此时 P 没有可运行的 G 提供 M 运行,那么 M 将解绑 P,并进入挂起状态。
当 I/O 操作 或者 channel操作完成,在等待队列中的 G 会被唤醒,标记为 runnable 状态,并进图某个 P 的队列中,绑定一个 M 后继续执行。 -
系统调用阻塞情况下的调度
如果 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代码问题的诊断都有很大帮助。
相关文章:

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/Htt…...

CUDA学习笔记5——CUDA程序错误检测
CUDA程序错误检测 所有CUDA的API函数都有一个类型为cudaError_t的返回值,代表了一种错误信息;只有返回cudaSuccess时,才是成功调用。 cudaGetLastError()用来检测核函数的执行是否出错cudaGetErrorString()输出错误信息 #include <stdi…...

虹科 | 解决方案 | 机械免拆压力测试方案
对于发动机的气门卡滞或气门开闭时刻错误、活塞环磨损、喷油嘴泄漏/堵塞等故障,往往需要解体发动机或拆卸部件才能发现;而对于某些轻微的故障,即使解体了发动机后也经常难于肉眼判别 虹科Pico提供的WPS500压力测试方案,可以动态测…...

Python数据挖掘实用案例——自动售货机销售数据分析与应用
🚀欢迎来到本文🚀 🍉个人简介:陈童学哦,目前学习C/C、算法、Python、Java等方向,一个正在慢慢前行的普通人。 🏀系列专栏:陈童学的日记 💡其他专栏:CSTL&…...

深度学习技巧应用29-软件设计模式与神经网络巧妙结合,如何快速记忆软件设计模式
大家好,我是微学AI,今天给大家介绍一下软件设计模式与神经网络巧妙结合,如何快速记忆软件设计模式。我们知道软件设计模式有23种,考试的时候经常会考到,但是这么种里面我们如何取判断它呢,如何去记忆它呢&a…...

中文编程开发语言工具应用案例:ps5体验馆计时收费管理系统软件
中文编程开发语言工具应用案例:ps5体验馆计时收费管理系统软件 软件部分功能: 1、计时计费功能:只需点开始计时即可,时间直观显示 2、商品管理功能:可以管理饮料等商品 3、会员管理功能:支持只用手机号作…...

绘制核密度估计图
简介 核密度估计图(Kernel Density Estimation,KDE)是一种用于估计数据分布的非参数方法,通常用于可视化和理解数据的分布情况。它通过平滑地估计数据的概率密度函数(PDF)来显示数据的分布特征,…...

基于深度学习网络的蔬菜水果种类识别算法matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1数据集准备 4.2构建深度学习模型 4.3模型训练 4.4模型评估 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 clc; clear; close all; wa…...

UE4 距离场
在项目设置的渲染模块可打开距离场 把该节点连上,该节点的意思是,距离表面越近,材质显示值为0 不接近表面时: 接近表面时 可勾选该值即可看到距离场具体效果: 未接触表面时: 接触表面时: 产生…...
【SA8295P 源码分析 (四)】26 - QNX Ethernet MAC 驱动 之 emac_rx_thread_handler 数据接收线程 源码分析
【SA8295P 源码分析】26 - QNX Ethernet MAC 驱动 之 emac_rx_thread_handler 数据接收线程 源码分析 一、emac_rx_thread_handler():通过POLL 轮询方式获取数据二、emac_rx_poll_mq():调用 pdata->clean_rx() 来处理消息三、emac_configure_rx_fun_ptr():配置 pdata->…...

VR全景广告:让消费者体验沉浸式交互,让营销更有趣
好的产品都是需要广告宣传的,随着科技的不断发展,市面上的广告也和多年前的传统广告不同,通过VR技术,可以让广告的观赏性以及科技感更加强烈,并且相比于视频广告,成本也更低。 在广告营销中,关键…...

论文阅读 | RAFT: Recurrent All-Pairs Field Transforms for Optical Flow
RAFT: Recurrent All-Pairs Field Transforms for Optical Flow ECCV2020光流任务best paper 论文地址:【here】 代码地址:【here】 介绍 光流是对两张相邻图像中的逐像素运动的一种估计。目前碰到的一些困难包括:物体的快速运动ÿ…...
神经网络的发展历史
神经网络的发展历史可以追溯到上世纪的数学理论和生物学研究。以下是神经网络发展史的详细概述: 早期的神经元模型: 1943年,Warren McCulloch和Walter Pitts提出了一种神经元模型,被称为MCP神经元模型,它模拟了生物神经…...
【单元测试】--单元测试最佳实践
一、单元测试代码风格 编写单元测试代码时,遵循一致的风格和最佳实践是非常重要的,因为它有助于提高代码的可读性、可维护性和可靠性。以下是一些常见的单元测试代码风格和最佳实践: 命名约定: 测试方法的名称应当清晰、描述性&…...

llava1.5-部署
llava1.5 ——demo部署 下载代码和权重 新建weights文件夹,并下载到LLaVA/weights/中。->需要修改文件名为llava-版本,例如llava-v1.5-7b. 运行 启动控制台 python -m llava.serve.controller --host 0.0.0.0 --port 4006启动gradio python -m…...

倒计时 1 天|KCD 2023 杭州站
距离「KCD 2023 杭州站」开始只有 1 天啦 大家快点预约到现场哦~ KCD 2023 活动介绍 HANGZHOU 关于 KCD Kubernetes Community Days(KCD)由云原生计算基金会(CNCF)发起,由全球各国当地的 CNCF 大使、CNCF 员…...

什么是模拟芯片,模拟芯片都有哪些测试指标?
模拟芯片又称处理模拟信号的集成电路 模拟集成电路主要是指由电容、电阻、晶体管等组成的模拟电路集成在一起用来处理模拟信号的集成电路。有许多的模拟集成电路,如运算放大器、模拟乘法器、锁相环、电源管理芯片等。 模拟集成电路的主要构成电路有:放…...

C++-json(2)-unsigned char-unsigned char*-memcpy-strcpy-sizeof-strlen
1.类型转换: //1.赋值一个不知道长度的字符串unsigned char s[] "kobe8llJfFwFSPiy"; //1.用一个字符串初始化变量 unsigned int s_length strlen((char*)s); //2.获取字符串长度//2.字符串里有双引号"" 需要…...

python安装第三方包
1 命令行下载 pip install 包名称 进入命令行输入该命令 由于pip是连接的国外的网站进行包的下载,所以有的时候会速度很慢。 我们可以通过如下命令,让其连接国内的网站进行包的安装: pip install -i https://pypi.tuna.tsinghua.edu.cn/s…...

《数据结构、算法与应用C++语言描述》-队列的应用-电路布线问题
《数据结构、算法与应用C语言描述》-队列的应用-电路布线问题 问题描述 在 迷宫老鼠问题中,可以寻找从迷宫入口到迷宫出口的一条最短路径。这种在网格中寻找最短路径的算法有许多应用。例如,在电路布线问题的求解中,一个常用的方法就是在布…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...