【Go并发编程】Goroutine 调度器揭秘:从 GMP 模型到 Work Stealing 算法

news2025/5/17 9:29:38

每天一篇Go语言干货,从核心到百万并发实战,快来关注魔法小匠,一起探索Go语言的无限可能!

在 Go 语言中,Goroutine 是一种轻量级的并发执行单元,它使得并发编程变得简单高效。而 Goroutine 的高效调度机制是 Go 语言在并发处理上的一大亮点。本文将深入剖析 Go 语言的 Goroutine 调度器,从 GMP 模型到 Work Stealing 算法,带你一探究竟。

一、Goroutine 调度器的背景

Go 语言的并发模型基于 Goroutine,它是一种轻量级的线程,由 Go 运行时(runtime)自动管理。Goroutine 的调度机制决定了多个 Goroutine 如何高效地映射到操作系统线程上执行。与传统线程(Thread)相比具有以下优势:

  1. 内存占用仅2KB(线程默认1MB)
  2. 上下文切换成本仅0.2μs(线程约1μs)
  3. 创建速度达到微秒级(线程需毫秒级)

但是Goroutine本质上是用户态线程,需要依赖GMP调度器将其映射到操作系统线程(M)执行。

二、GMP 模型:Goroutine 调度的核心

Goroutine 的调度基于 GMP 模型,即 Goroutine(G)、Machine(M)和 P(Processor)的组合。这个模型实现了从 N:1(用户态线程到内核态线程)到 N:M(用户态线程到内核态线程的灵活映射)的调度。

1. Goroutine(G)

Goroutine 是用户定义的协程,它代表了并发执行的任务。创建 Goroutine 的底层方法是newproc 函数,它会将 Goroutine 放入 P 的本地队列中。如果本地队列已满,则放入全局队列中。

go func() {
    // 任务代码
}()
2. Machine(M)

Machine 代表操作系统线程,是 Go 运行时与操作系统交互的接口。Go 运行时会根据需要创建和销毁 M,以适应不同的并发场景。

3. Processor(P)

Processor 是 Go 运行时中的调度上下文,它负责管理 Goroutine 的调度。每个 P 有自己的本地队列,用于存储待执行的 Goroutine。

4.GMP模型示意图

通过该示意图可以了解到完整的GMP模型关系。

全局队列:本地队列(Processor调度器管理)满了的情况下,将会把新创建的Goroutine加入到全局队列中排队等待执行。

本地队列:存放即将执行的Goroutine,每个processor中的goroutine将并行执行。

Goroutine(G):图中的每个圆形图标G就是代表一个Groutine。

Processor(P):管理当前调度器内的本地队列,并负责管理 Goroutine 的调度,用于存储待执行的 Goroutine。processor和groutine是N:M的关系。

内核线程(M):每个M代表了一个内核线程,操作系统调度器负责把内核线程分配到CPU的核上执行。

三、调度器的工作流程

Go 调度器的核心任务是从队列中获取可执行的 Goroutine,并将其分配给可用的 M 执行。

1. 本地队列

每个 P 都有一个本地队列,调度器会优先从本地队列中获取 Goroutine 执行。如果本地队列为空,则会尝试从全局队列获取。

2. 全局队列

全局队列是所有 P 共享的队列,用于存储未被分配的 Goroutine。当本地队列为空时,调度器会尝试从全局队列中获取 Goroutine。

3. Work Stealing(工作窃取)

如果本地队列和全局队列都为空,调度器会采用 Work Stealing 算法,从其他 P 的本地队列中“偷取” Goroutine。这种策略可以实现线程之间的负载均衡。

func runqsteal(pp, p2 *p, stealRunNextG bool) *g {
    t := pp.runqtail
    n := runqgrab(p2, &pp.runq, t, stealRunNextG)
    if n == 0 {
        return nil
    }
    n--
    gp := pp.runq[(t+n)%uint32(len(pp.runq))].ptr()
    if n == 0 {
        return gp
    }
    h := atomic.LoadAcq(&pp.runqhead)
    if t-h+n >= uint32(len(pp.runq)) {
        throw("runqsteal: runq overflow")
    }
    atomic.StoreRel(&pp.runqtail, t+n)
    return gp
}

四、抢占式调度

Go 调度器采用抢占式调度策略,以防止某个 Goroutine 占用过多 CPU 资源。在 Go 1.14 之后,调度器在任何安全点都可以进行抢占。

func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
    mp := getg().mtop
    pp := mp.p.ptr()
    // 每61次调度周期就检查一次全局G队列
    if pp.schedtick%61 == 0 && sched.runqsize > 0 {
        lock(&sched.lock)
        gp := globrunqget(pp, 1)
        unlock(&sched.lock)
        if gp != nil {
            return gp, false, false
        }
    }
    // 本地队列
    if gp, inheritTime := runqget(pp); gp != nil {
        return gp, inheritTime, false
    }
    // 全局队列
    if sched.runqsize != 0 {
        lock(&sched.lock)
        gp := globrunqget(pp, 0)
        unlock(&sched.lock)
        if gp != nil {
            return gp, false, false
        }
    }
    // 工作窃取
    if mp.spinning || 2*sched.nmspinning.Load() < gomaxprocs-sched.npidle.Load() {
        if !mp.spinning {
            mp.becomeSpinning()
        }
        gp, inheritTime, _, _, _ := stealWork(now)
        if gp != nil {
            return gp, inheritTime, false
        }
    }
    return nil, false, false
}

五、协作式调度

除了抢占式调度,Go 还支持协作式调度。Goroutine 可以通过调用runtime.Gosched() 函数主动让出 CPU 的执行权。

func main() {
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println("Goroutine 1")
            runtime.Gosched()
        }
    }()
    for i := 0; i < 10; i++ {
        fmt.Println("Goroutine 2")
    }
}

六、总结

Go 语言的 Goroutine 调度机制通过 GMP 模型和 Work Stealing 算法实现了高效的并发执行。抢占式调度和协作式调度策略确保了 Goroutine 的公平执行,而 Work Stealing 算法则进一步提高了多核处理器上的负载均衡。通过这些机制,Go 运行时能够高效地利用系统资源,实现高性能的并发编程。

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

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

相关文章

【前端】Vue组件库之Element: 一个现代化的 UI 组件库

文章目录 前言一、官网1、官网主页2、设计原则3、导航4、组件 二、核心功能&#xff1a;开箱即用的组件生态1、丰富的组件体系2、特色功能亮点 三、快速上手&#xff1a;三步开启组件化开发1、安装&#xff08;使用Vue 3&#xff09;2、全局引入3、按需导入&#xff08;推荐&am…

坐井说天阔---DeepSeek-R1

前言 DeepSeek-R1这么火&#xff0c;虽然网上很多介绍和解读&#xff0c;但听人家的总不如自己去看看原论文。于是花了大概一周的时间&#xff0c;下班后有进入了研究生的状态---读论文。 DeepSeek这次的目标是探索在没有任何监督数据的情况下训练具有推理能力的大模型&#…

UART(一)——UART基础

一、定义 UART(Universal Asynchronous Receiver/Transmitter)是一种广泛使用的串行通信协议,用于在设备间通过异步方式传输数据。它无需共享时钟信号,而是依赖双方预先约定的参数(如波特率)完成通信。 功能和特点 基本的 UART 系统只需三个信号即可提供稳健的中速全双工…

DeepSeek 的创新融合:多行业应用实践探索

引言 在数字化转型的浪潮中&#xff0c;技术的融合与创新成为推动各行业发展的关键力量。蓝耘平台作为行业内备受瞩目的创新平台&#xff0c;以其强大的资源整合能力和灵活的架构&#xff0c;为企业提供了高效的服务支持。而 DeepSeek 凭借先进的人工智能技术&#xff0c;在自然…

CentOS 7超详细安装教程(含镜像)

1. 安装前准备 1.1 CentOS简介 CentOS&#xff08;Community Enterprise Operating System&#xff0c;中文意思是&#xff1a;社区企业操作系统&#xff09;是一种基于 Red Hat Enterprise Linux&#xff08;RHEL&#xff09;源代码构建的免费开源操作系统。它在稳定性、安全…

Qt中基于开源库QRencode生成二维码(附工程源码链接)

目录 1.QRencode简介 2.编译qrencode 3.在Qt中直接使用QRencode源码 3.1.添加源码 3.2.用字符串生成二维码 3.3.用二进制数据生成二维码 3.4.界面设计 3.5.效果展示 4.注意事项 5.源码下载 1.QRencode简介 QRencode是一个开源的库&#xff0c;专门用于生成二维码&…

SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪

SpringBoot教程&#xff08;三十二&#xff09; | SpringBoot集成Skywalking链路跟踪 一、Skywalking是什么&#xff1f;二、Skywalking与JDK版本的对应关系三、Skywalking下载四、Skywalking 数据存储五、Skywalking 的启动六、部署探针 前提&#xff1a; Agents 8.9.0 放入 …

IntelliJ IDEA 接入 AI 编程助手(Copilot、DeepSeek、GPT-4o Mini)

IntelliJ IDEA 接入 AI 编程助手&#xff08;Copilot、DeepSeek、GPT-4o Mini&#xff09; &#x1f4ca; 引言 近年来&#xff0c;AI 编程助手已成为开发者的高效工具&#xff0c;它们可以加速代码编写、优化代码结构&#xff0c;并提供智能提示。本文介绍如何在 IntelliJ I…

【机器学习】深入浅出KNN算法:原理解析与实践案例分享

在机器学习中&#xff0c;K-最近邻算法&#xff08;K-Nearest Neighbors, KNN&#xff09;是一种既直观又实用的算法。它既可以用于分类&#xff0c;也可以用于回归任务。本文将简单介绍KNN算法的基本原理、优缺点以及常见应用场景&#xff0c;并通过一个简单案例帮助大家快速入…

vscode的一些实用操作

1. 焦点切换(比如主要用到使用快捷键在编辑区和终端区进行切换操作) 2. 跳转行号 使用ctrl g,然后输入指定的文件内容&#xff0c;即可跳转到相应位置。 使用ctrl p,然后输入指定的行号&#xff0c;回车即可跳转到相应行号位置。

JavaEE基础 Tomcat与Http (下)

目录 1.HTTP 协议 1.1 HTTP 协议概念 1.2. 无状态协议 1.3. HTTP1.0 和 HTTP1.1 1.4 请求协议和响应协议 ​编辑 1.5 请求协议 1.5.1 常见的请求协议 1.5.2 GET 请求 1.5.3 POST请求 1.5.4 响应协议 1.HTTP 协议 Http浏览器访问东西都是遵循的Http协议。 1.1 HTTP 协议…

【Linux】【进程】epoll内核实现总结+ET和LT模式内核实现方式

【Linux】【网络】epoll内核实现总结ET和LT模式内核实现方式 1.epoll的工作原理 eventpoll结构 当某一进程调用epoll_create方法时&#xff0c;Linux内核会创建一个eventpoll结构体&#xff0c;这个结构体中有两个成员与epoll的使用方式密切相关. struct eventpoll{..../*红…

英码科技基于昇腾算力实现DeepSeek离线部署

DeepSeek-R1 模型以其创新架构和高效能技术迅速成为行业焦点。如果能够在边缘进行离线部署&#xff0c;不仅能发挥DeepSeek大模型的效果&#xff0c;还能确保数据处理的安全性和可控性。 英码科技作为AI算力产品和AI应用解决方案服务商&#xff0c;积极响应市场需求&#xff0…

【SQL】SQL约束

&#x1f384;约束 &#x1f4e2;作用:是用于限制存储再表中的数据。可以再创建表/修改表时添加约束。 &#x1f4e2;目的:保证数据库中数据的正确、有效性和完整性。 &#x1f4e2;对于一个字段可以同时添加多个约束。 &#x1f384;常用约束: 约束分类 约束 描述关键字非…

解决 `pip is configured with locations that require TLS/SSL` 错误

问题描述 在使用 pip 安装 Python 包时&#xff0c;可能会遇到以下错误&#xff1a; WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.这意味着 Python 的 ssl 模块未正确安装或配置&#xff0c;导致 p…

Python 面向对象的三大特征

前言&#xff1a;本篇讲解面向对象的三大特征&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff0c;还有比较细致的&#xff08;类属性类方法&#xff0c;静态方法&#xff09;&#xff0c;分步骤讲解&#xff0c;比较适合理清楚三大特征的思路 面向对象的…

机器学习_18 K均值聚类知识点总结

K均值聚类&#xff08;K-means Clustering&#xff09;是一种经典的无监督学习算法&#xff0c;广泛应用于数据分组、模式识别和降维等领域。它通过将数据划分为K个簇&#xff0c;使得簇内相似度高而簇间相似度低。今天&#xff0c;我们就来深入探讨K均值聚类的原理、实现和应用…

从低清到4K的魔法:FlashVideo突破高分辨率视频生成计算瓶颈(港大港中文字节)

论文链接&#xff1a;https://arxiv.org/pdf/2502.05179 项目链接&#xff1a;https://github.com/FoundationVision/FlashVideo 亮点直击 提出了 FlashVideo&#xff0c;一种将视频生成解耦为两个目标的方法&#xff1a;提示匹配度和视觉质量。通过在两个阶段分别调整模型规模…

Nuclei 使用手册

Nuclei 是一个开源的快速、高效的漏洞扫描工具&#xff0c;主要用于网络安全领域的漏洞检测。它由 go 语言开发&#xff0c;设计目的是为了高效地扫描 Web 应用程序、网络服务等目标&#xff0c;帮助安全研究人员、渗透测试人员以及红队成员发现潜在的漏洞。 下载链接&#xf…

python学opencv|读取图像(六十七)使用cv2.convexHull()函数实现图像轮廓凸包标注

【1】引言 前序学习进程中&#xff0c;已经初步探索了对图像轮廓的矩形标注和圆形标注&#xff1a; python学opencv|读取图像&#xff08;六十五&#xff09;使用cv2.boundingRect()函数实现图像轮廓矩形标注-CSDN博客 但实际上&#xff0c;这两种标注方法都是大致的&#x…