GoLang协程Goroutiney原理与GMP模型详解
本文原文地址:GoLang协程Goroutiney原理与GMP模型详解
什么是goroutine
Goroutine是Go语言中的一种轻量级线程,也成为协程,由Go运行时管理。它是Go语言并发编程的核心概念之一。Goroutine的设计使得在Go中实现并发编程变得非常简单和高效。
以下是一些关于Goroutine的关键特性:
- 轻量级:Goroutine的创建和切换开销非常小。与操作系统级别的线程相比,Goroutine占用的内存和资源更少。一个典型的Goroutine只需要几KB的栈空间,并且栈空间可以根据需要动态增长。
- 并发执行:Goroutine可以并发执行多个任务。Go运行时会自动将Goroutine调度到可用的处理器上执行,从而充分利用多核处理器的能力。
- 简单的语法:启动一个Goroutine非常简单,只需要在函数调用前加上go关键字。例如,go myFunction()会启动一个新的Goroutine来执行myFunction函数。
- 通信和同步:Go语言提供了通道(Channel)机制,用于在Goroutine之间进行通信和同步。通道是一种类型安全的通信方式,可以在不同的Goroutine之间传递数据。
什么是协程
协程(Coroutine)是一种比线程更轻量级的并发编程方式。它允许在单个线程内执行多个任务,并且可以在任务之间进行切换,而不需要进行线程上下文切换的开销。协程通过协作式多任务处理来实现并发,这意味着任务之间的切换是由程序显式控制的,而不是由操作系统调度的。
以下是协程的一些关键特性:
- 轻量级:协程的创建和切换开销非常小,因为它们不需要操作系统级别的线程管理。
- 非抢占式:协程的切换是显式的,由程序员在代码中指定,而不是由操作系统抢占式地调度。
- 状态保存:协程可以在暂停执行时保存其状态,并在恢复执行时继续从暂停的地方开始。
- 异步编程:协程非常适合用于异步编程,特别是在I/O密集型任务中,可以在等待I/O操作完成时切换到其他任务,从而提高程序的并发性和效率。
Goroutin就是Go在协程这个场景上的实现。
以下是一个简单的go goroutine例子,展示了如何使用协程:
package mainimport ("fmt""sync""time"
)// 定义一个简单的函数,模拟一个耗时操作
func printNumbers(wg *sync.WaitGroup) {defer wg.Done() // 在函数结束时调用Done方法for i := 1; i <= 5; i++ {fmt.Printf("Number: %d\n", i)time.Sleep(1 * time.Second) // 模拟耗时操作}
}func main() {var wg sync.WaitGroup// 启动一个goroutine来执行printNumbers函数wg.Add(1)go printNumbers(&wg)// 主goroutine继续执行其他操作for i := 'A'; i <= 'E'; i++ {fmt.Printf("Letter: %c\n", i)time.Sleep(1 * time.Second) // 模拟耗时操作}// 等待所有goroutine完成wg.Wait()
}
我们定义了一个名为printNumbers的函数,该函数会打印数字1到5,并在每次打印后暂停1秒。然后,在main函数中,我们使用go关键字启动一个新的goroutine来执行printNumbers函数。同时,主goroutine继续执行其他操作,打印字母A到E,并在每次打印后暂停1秒。
需要注意的是,主goroutine和新启动的goroutine是并发执行的。为了确保所有goroutine完成,我们使用sync.WaitGroup来等待所有goroutine完成。我们在启动goroutine之前调用wg.Add(1),并在printNumbers函数结束时调用wg.Done()。最后,我们在main函数中调用wg.Wait(),等待所有goroutine完成。这样可以确保程序在所有goroutine完成之前不会退出。
协程是一种强大的工具,可以简化并发编程,特别是在处理I/O密集型任务时。
Goroutin实现原理
Goroutine的实现原理包括Goroutine的创建、调度、上下文切换和栈管理等多个方面。通过GPM模型和高效的调度机制,Go运行时能够高效地管理和调度大量的Goroutine,实现高并发编程。
Goroutine的创建
当使用go关键字启动一个新的Goroutine时,Go运行时会执行以下步骤:
- 分配G结构体:Go运行时会为新的Goroutine分配一个G结构体(G表示Goroutine),其中包含Goroutine的状态信息、栈指针、程序计数器等。
- 分配栈空间:Go运行时会为新的Goroutine分配初始的栈空间,通常是几KB。这个栈空间是动态增长的,可以根据需要自动扩展。
- 初始化G结构体:Go运行时会初始化G结构体,将Goroutine的入口函数、参数、栈指针等信息填入G结构体中。
- 将Goroutine加入调度队列:Go运行时会将新的Goroutine加入到某个P(Processor)的本地运行队列中,等待调度执行。
Goroutine的调度
Go运行时使用GPM模型(Goroutine、Processor、Machine)来管理和调度Goroutine。调度过程如下:
- P(Processor):P是Go运行时的一个抽象概念,表示一个逻辑处理器。每个P持有一个本地运行队列,用于存储待执行的Goroutine。P的数量通常等于机器的CPU核心数,可以通过runtime.GOMAXPROCS函数设置。
- M(Machine):M表示一个操作系统线程。M负责实际执行P中的Goroutine。M与P是一对一绑定的关系,一个M只能绑定一个P,但一个P可以被多个M绑定(通过抢占机制)。M的数量是由Go运行时系统动态管理和确定的。M的数量并不是固定的,而是根据程序的运行情况和系统资源的使用情况动态调整的。通过runtime.NumGoroutine()和runtime.NumCPU()函数,我们可以查看当前的Goroutine数量和CPU核心数。Go运行时对M的数量有一个默认的最大限制,以防止创建过多的M导致系统资源耗尽。这个限制可以通过环境变量GOMAXPROCS进行调整,但通常不需要手动设置。
- G(Goroutine):代表一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度。
- 调度循环:每个P会在一个循环中不断从本地运行队列中取出Goroutine,并将其分配给绑定的M执行。如果P的本地运行队列为空,P会尝试从其他P的本地运行队列中窃取Goroutine(工作窃取机制)。

从上图中看,有2个物理线程M,每一个M都拥有一个处理器P,每一个也都有一个正在运行的goroutine。P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。图中灰色的那些goroutine并没有运行,而是出于ready的就绪态,正在等待被调度。P维护着这个队列(称之为runqueue),Go语言里,启动一个goroutine很容易:go function 就行,所以每有一个go语句被执行,runqueue队列就在其末尾加入一个goroutine,在下一个调度点,就从runqueue中取出(如何决定取哪个goroutine?)一个goroutine执行。
P的数量可以大于器的CPU核心数?
在Go语言中,P(Processor)的数量通常等于机器的CPU核心数,但也可以通过runtime.GOMAXPROCS函数进行调整。默认情况下,Go运行时会将P的数量设置为机器的逻辑CPU核心数。然而,P的数量可以被设置为大于或小于机器的CPU核心数,这取决于具体的应用需求和性能考虑。
调整P的数量,可以使用runtime.GOMAXPROCS函数来设置P的数量。例如:
package mainimport ("fmt""runtime""sync"
)func worker(id int, wg *sync.WaitGroup) {defer wg.Done()fmt.Printf("Worker %d starting\n", id)// 模拟工作负载for i := 0; i < 1000000000; i++ {}fmt.Printf("Worker %d done\n", id)
}func main() {// 设置P的数量为机器逻辑CPU核心数的两倍numCPU := runtime.NumCPU()runtime.GOMAXPROCS(numCPU * 2)var wg sync.WaitGroup// 启动多个Goroutinefor i := 1; i <= 10; i++ {wg.Add(1)go worker(i, &wg)}// 等待所有Goroutine完成wg.Wait()fmt.Println("All workers done")
}
在这个示例中,我们将P的数量设置为机器逻辑CPU核心数的两倍。这样做的目的是为了观察在不同P数量设置下程序的性能表现。
- P的数量大于CPU核心数的影响
- 上下文切换增加:当P的数量大于CPU核心数时,可能会导致更多的上下文切换。因为操作系统需要在有限的CPU核心上调度更多的线程(M),这可能会增加调度开销。
- 资源竞争:更多的P意味着更多的Goroutine可以同时运行,但这也可能导致更多的资源竞争,特别是在I/O密集型任务中。过多的P可能会导致资源争用,反而降低程序的整体性能。
- 并发性提高:在某些情况下,增加P的数量可以提高程序的并发性,特别是在存在大量阻塞操作(如I/O操作)的情况下。更多的P可以更好地利用CPU资源,减少阻塞时间。
- P的数量小于CPU核心数的影响
- CPU利用率降低:当P的数量小于CPU核心数时,可能会导致CPU资源未被充分利用。因为P的数量限制了同时运行的Goroutine数量,可能会导致某些CPU核心处于空闲状态。
- 减少上下文切换:较少的P数量可以减少上下文切换的开销,因为操作系统需要调度的线程(M)数量减少。这可能会提高CPU密集型任务的性能。
选择合适的P数量选择合适的P数量需要根据具体的应用场景和性能需求进行调整。以下是一些建议:
- CPU密集型任务:对于CPU密集型任务,通常将P的数量设置为等于或接近机器的逻辑CPU核心数,以充分利用CPU资源。
- I/O密集型任务:对于I/O密集型任务,可以考虑将P的数量设置为大于CPU核心数,以提高并发性和资源利用率。
- 性能测试和调优:通过性能测试和调优,找到最佳的P数量设置。可以尝试不同的P数量,观察程序的性能表现,选择最优的配置。
Goroutine的上下文切换
Goroutine的上下文切换由Go运行时的调度器管理,主要涉及以下步骤:
- 保存当前Goroutine的状态:当一个Goroutine被挂起时,Go运行时会保存当前Goroutine的状态信息,包括程序计数器、栈指针、寄存器等。
- 切换到新的Goroutine:Go运行时会从P的本地运行队列中取出下一个待执行的Goroutine,并恢复其状态信息。
- 恢复新的Goroutine的状态:Go运行时会将新的Goroutine的状态信息加载到CPU寄存器中,并跳转到新的Goroutine的程序计数器位置,继续执行。
Goroutine什么时候会被挂起?Goroutine会在执行阻塞操作、使用同步原语、被调度器调度、创建和销毁时被挂起。Go运行时通过高效的调度机制管理Goroutine的挂起和恢复,以实现高并发和高性能的程序执行。了解这些挂起的情况有助于编写高效的并发程序,并避免潜在的性能问题。
- 阻塞操作
当Goroutine执行阻塞操作时,它会被挂起,直到阻塞操作完成。常见的阻塞操作包括:
- I/O操作:如文件读写、网络通信等。
- 系统调用:如调用操作系统提供的阻塞函数。
- Channel操作:如在无缓冲Channel上进行发送或接收操作时,如果没有对应的接收者或发送者,Goroutine会被挂起。
- 同步原语
使用同步原语(如sync.Mutex、sync.WaitGroup、sync.Cond等)进行同步操作时,Goroutine可能会被挂起,直到条件满足。例如:
- 互斥锁(Mutex):当Goroutine尝试获取一个已经被其他Goroutine持有的互斥锁时,它会被挂起,直到锁被释放。
- 条件变量(Cond):当Goroutine等待条件变量时,它会被挂起,直到条件变量被通知。
- 调度器调度
Go运行时的调度器会根据需要挂起和恢复Goroutine,以实现高效的并发调度。调度器可能会在以下情况下挂起Goroutine:
- 时间片用完:Go调度器使用协作式调度,当一个Goroutine的时间片用完时,调度器会挂起该Goroutine,并调度其他Goroutine执行。
- 主动让出:Goroutine可以通过调用runtime.Gosched()主动让出CPU,调度器会挂起该Goroutine,并调度其他Goroutine执行。
- Goroutine的创建和销毁
- 创建:当一个新的Goroutine被创建时,它会被挂起,直到调度器将其调度执行。
- 销毁:当一个Goroutine执行完毕或被显式终止时,它会被挂起并从调度器中移除。
Goroutine的栈管理
Goroutine的栈空间是动态分配的,可以根据需要自动扩展。Go运行时使用分段栈(segmented stack)或连续栈(continuous stack)来管理Goroutine的栈空间:
- 分段栈:在早期版本的Go中,Goroutine使用分段栈。每个Goroutine的栈由多个小段组成,当栈空间不足时,Go运行时会分配新的栈段并链接到现有的栈段上。
- 连续栈:在Go 1.3及以后的版本中,Goroutine使用连续栈。每个Goroutine的栈是一个连续的内存块,当栈空间不足时,Go运行时会分配一个更大的栈,并将现有的栈内容复制到新的栈中。
相关文章:
GoLang协程Goroutiney原理与GMP模型详解
本文原文地址:GoLang协程Goroutiney原理与GMP模型详解 什么是goroutine Goroutine是Go语言中的一种轻量级线程,也成为协程,由Go运行时管理。它是Go语言并发编程的核心概念之一。Goroutine的设计使得在Go中实现并发编程变得非常简单和高效。 以下是一些…...
全文检索ElasticSearch到底是什么?
学习ElasticSearch之前,我们先来了解一下搜索 1 搜索是什么 ① 概念:用户输入想要的关键词,返回含有该关键词的所有信息。 ② 场景: 1互联网搜索:谷歌、百度、各种新闻首页; 2 站内搜索ÿ…...
FPGA实现串口升级及MultiBoot(五)通过约束脚本添加IPROG实例
本文目录索引 一个指令和三种方式通过约束脚本添加Golden位流工程MultiBoot位流工程验证example1总结代码缩略词索引: K7:Kintex 7V7:Vertex 7A7:Artix 7MB:MicroBlaze上一篇文章种总结了MultiBoot 关键技术,分为:一个指令、二种位流、三种方式、四样错误。针对以上四句话我…...
文献阅读 | Nature Methods:使用 STAMP 对空间转录组进行可解释的空间感知降维
文献介绍 文献题目: 使用 STAMP 对空间转录组进行可解释的空间感知降维 研究团队: 陈金妙(新加坡科学技术研究局) 发表时间: 2024-10-15 发表期刊: Nature Methods 影响因子: 36.1࿰…...
【模块化大作战】Webpack如何搞定CommonJS与ES6混战(1-3)
在前端开发中,模块化是一个重要的概念,不同的模块化标准有不同的特点和适用场景。webpack 同时支持 CommonJS 和 ES6 Module,因此需要理解它们在互操作时 webpack 是如何处理的。 同模块化标准 如果导出和导入使用的是同一种模块化标准&…...
[NewStar 2024] week5完结
每次都需要用手机验证码登录,题作的差不多就没再进过。今天把week5解出的部分记录下。好像时间过去很久了。 Crypto 没e也能完 这题给了e,p,q,dp,dq。真不清楚还缺啥 long_to_bytes(pow(c,dp,p)) 格格你好棒 给了a,b和提示((p2*r) * 3*a q) % b < 70 其中r…...
IntelliJ IDEA的快捷键
IntelliJ IDEA 是一个非常强大的集成开发环境,它提供了大量的快捷键来加速开发者的日常工作。这里为您整理了一份 IntelliJ IDEA 的快捷键大全,包含了编辑、导航、重构、运行等多个方面的快捷键。请注意,这些快捷键是基于 Windows 版本的 Int…...
暮雨直播 1.3.2 | 内置直播源,频道丰富,永久免费
暮雨直播是一款内置直播源的电视直播应用程序,提供丰富的频道内容,包括教学、首页、一线、博主、解说、动漫、堆堆等。该应用的内置直播源持续更新维护,确保用户可以稳定地观看各种电视频道。暮雨直播承诺永久免费,为用户提供了一…...
单相锁相环,原理与Matlab实现
单相锁相环基本原理 单相锁相环的基本原理图如下所示, u α u_\alpha uα u β u_\beta uβ经Park变换、PI控制实现对角频率 ω \omega ω和角度 θ \theta θ的估算。不同锁相环方案之间的差异,主要表现在正交电压 u β u_\beta uβ的生成&#x…...
PICO+Unity 用手柄点击UI界面
如果UI要跟随头显,可将Canvas放置到XR Origin->Camera Offset->Main Camera下 1.Canvas添加TrackedDeviceGraphicRaycaster组件 2.EventSystem移动默认的Standard Input Module,添加XRUIInputModule组件 3.(可选)设置射线可…...
Rust移动开发:Rust在iOS端集成使用介绍
iOS调用Rust 上篇介绍了 Rust移动开发:Rust在Android端集成使用介绍, 这篇主要看下iOS上如何使用Rust,Rust可以给移动端开发提供跨平台,通用组件支持。 该篇适合对iOS、Rust了解,想知道如何整合调用和编译的,如果想要…...
虚拟现实技术在旅游行业的应用
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 虚拟现实技术在旅游行业的应用 虚拟现实技术在旅游行业的应用 虚拟现实技术在旅游行业的应用 引言 虚拟现实技术概述 定义与原理…...
《Java核心技术 卷I》Swing使用颜色
使用颜色 使用Graphics2D类的setPaint方法可以为图形上下文上的所有后续的绘制操作选择颜色。例如: g2.setPaint(Color.RED); g2.drawString("Warning!",100,100); 可以用一种颜色填充一个封闭图像(例如:矩形或椭圆)的内部。为此ÿ…...
神书《从零构建大模型》分享,尚未发布,GitHub标星22k!!
《从零构建大模型》是一本即将于今年10月底发布的书籍,github已经吸引了惊人的21.7k标星!作者是威斯康星大学麦迪逊分校的终身教授,在GitHub、油管、X上拥有大量粉丝,是一位真正的大佬。 本书免费获取地址 在本书中࿰…...
【JavaEE进阶】Spring AOP 原理
在之前的博客中 【JavaEE进阶】Spring AOP使用篇_aop多个切点-CSDN博客 我们主要学习了SpringAOP的应用, 接下来我们来学习SpringAOP的原理, 也就是Spring是如何实现AOP的. SpringAOP 是基于动态代理来实现AOP的,咱们学习内容主要分以下两部分 1.代理模式 2.Spring AOP源码剖…...
【网络安全】2.3 安全的网络设计_2.防御深度原则
文章目录 一、网络架构二、网络设备三、网络策略四、处理网络安全事件五、实例学习:安全的网络设计结论 网络设计是网络安全的基础,一个好的网络设计可以有效的防止攻击者的入侵。在本篇文章中,我们将详细介绍如何设计一个安全的网络&#…...
测绘程序设计|C#字符串及其操作|分割|取子串|格式化数值|StringBuilder类
由于微信公众号改变了推送规则,为了每次新的推送可以在第一时间出现在您的订阅列表中,记得将本公众号设为星标或置顶喔~ 简单介绍了C#字符串分割、取子串、拼接、格式化数值以及StringBuilder类,拿捏测绘程序设计大赛~ 🌿前言 字…...
自然语言处理——Hugging Face 详解
Hugging Face 是一个以自然语言处理(NLP)为核心的人工智能平台和开源社区,提供了一系列非常流行的机器学习工具和预训练模型,尤其在文本生成、分类、翻译、情感分析等任务中表现出色。Hugging Face 旗下最为著名的项目是 Transfor…...
本地保存mysql凭据实现免密登录mysql
本地保存mysql凭据 mysql加密登录文件简介加密保存mysql用户的密码到本地凭据 mysql加密登录文件简介 要在 mysql客户端 上连接 MySQL 而无需在命令提示符上输入用户名和口令,下列三个位置可用于存储用户的mysql 凭证来满足此要求。 配置文件my.cnf或my.ini /etc…...
Ubuntu 22 安装 Apache Doris 3.0.3 笔记
Ubuntu 22 安装 Apache Doris 3.0.3 笔记 1. 环境准备 Doris 需要 Java 17 作为运行环境,所以首先需要安装 Java 17。 sudo apt-get install openjdk-17-jdk -y sudo update-alternatives --config java在安装 Java 17 后,可以通过 sudo update-alter…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...
