Golang 中 Goroutine 的调度

news2025/1/3 10:19:42

Golang 中 Goroutine 的调度

Golang 中的 Goroutine 是一种轻量级的线程,由 Go 运行时(runtime)自动管理。Goroutine 的调度基于 M:N 模型,即多个 Goroutine 可以映射到多个操作系统线程上执行。以下是详细的调度过程和策略:

1. 创建 Goroutine
  • 使用 go 关键字可以创建一个新的 Goroutine。例如:
  go func() {
      // 任务代码
  }()
  • 创建 Goroutine 的底层方法是 newproc 函数,该函数会创建一个新的 Goroutine 并将其放入 P 的本地队列中。如果本地队列已满,则放入全局队列中。
2. 调度器(Scheduler)
  • Go 运行时包含一个调度器,负责管理 Goroutine 的执行。调度器的主要任务是从全局队列或本地队列获取可执行的 Goroutine,并将其分配给可用的线程(M)执行。
  • 调度器采用队列轮转法,确保每个 Goroutine 都有机会被执行。具体来说,调度器会从 P 的本地队列中获取 Goroutine 执行;如果本地队列为空,则从全局队列获取;如果全局队列也为空,则从其他 P 的本地队列中“偷取”一半数量的 Goroutine(称为 work stealing)。
3. Goroutine 的状态
  • Goroutine 可能处于多种状态,包括 _Grunning(正在运行)、_Gwaiting(等待中)、_Gblocked(阻塞中)等。
  • 当一个 Goroutine 完成其任务后,会调用 goexit 函数,该函数会将当前 Goroutine 放入 P 的复用链表中,并调用 schedule() 函数继续调度下一个 Goroutine。
4. 抢占式调度
  • Go 调度器是抢占式的,每个 Goroutine 最多执行 10ms 后会被替换。如果 Goroutine 运行超过 10ms,调度器会设置“抢占标志位”,但这一机制仅在有函数调用的情况下生效。
  • 当 Goroutine 发生阻塞(如等待通道、垃圾回收、sleep 休眠、锁等待、IO 阻塞等),调度器会将当前 Goroutine 调度走,让其他 Goroutine 来执行。
5. 调度时机
  • 调度时机包括但不限于:Goroutine 完成任务、发生阻塞、系统调用、垃圾回收等。
  • sysmon 是一个专门用于监控和管理 Goroutine 的线程,它记录所有 P 的 G 任务数量,并使用 schedtick 变量进行计数。如果 schedtick 一直没有递增,说明该 P 一直在执行同一个任务;如果持续超过 10ms,则会在该 G 任务的栈信息上加一个标记,G 任务在执行时检查此标记并中断自己,将自己添加到队列末尾,等待下一个 G 任务执行。
6. 核心数据结构
  • Go 运行时维护了三个核心数据结构:G(Goroutine)、P(Processor)、M(Machine)。
    • G 表示 Goroutine,每个 Goroutine 对应一个 G 结构体,存储其运行堆栈、状态和任务函数。
    • P 表示 Processor,相当于 CPU 核,为 G 提供执行环境。
    • M 是 OS 线程抽象,负责调度任务,代表真正执行计算的资源。
7. 调度流程
  • 当一个程序启动时,只有一个主 Goroutine 来调用 main 函数。在运行过程中,可以通过 go 关键字创建新的 Goroutine。
  • M 需要绑定一个 P 才能被调度执行,并在绑定后进入 schedule 循环,从全局队列或 P 的本地队列获取 Goroutine 并执行。
  • 当 M 执行完一个 Goroutine 后,会调用 goexitgoexit1 函数,保存当前 Goroutine 的上下文,切换到 g0 及其栈,调用传入的方法。最后,goexit0 函数清零 Goroutine 属性,状态从 _Grunning 改为 _Gdead,解绑 M 和 Goroutine,放入队列,重新调度。

总结

Golang 的 Goroutine 调度机制通过 M:N 模型实现了高效的并发执行。调度器负责管理 Goroutine 的生命周期和执行顺序,确保每个 Goroutine 都有机会被执行。通过抢占式调度和 work stealing 策略,Go 运行时能够高效地利用系统资源,实现高性能的并发编程。

Goroutine 和 OS 线程之间的映射机制是如何工作的?

Goroutine 和 OS 线程之间的映射机制主要通过 Go 运行时的调度器实现,采用 M:N 模型。这种模型允许将多个 goroutine 映射到较少数量的 OS 线程上,从而提高并发执行的效率。

具体来说,Go 运行时内部包含三个关键结构:M(Machine,即 OS 线程)、G(Goroutine)和 P(调度上下文)。M 代表真正的内核线程,G 代表用户态定义的协程,而 P 则负责调度,实现从 N:1 到 N:M 的用户空间线程与内核空间线程的映射。

在 Go 程序启动时,会创建一个或多个 OS 线程,并在这些线程上调度执行 goroutine。当一个 goroutine 需要执行时,Go 运行时会从线程池中选出一个可用的 M 或者新建一个 M。当一个 goroutine 阻塞(如进行 I/O 操作)时,Go 运行时可以将 P 转移到另一个 M 上继续执行其他 goroutine,从而提高 CPU 的利用率。

此外,Go 运行时还引入了 GOMAXPROCS 配置参数,它决定了 Go 代码可以同时执行的 OS 线程数量。通过调整该参数,开发者可以优化应用性能,但需平衡过高设置可能导致的资源竞争和上下文切换开销增加。

Go 调度器在不同操作系统(如 Linux 和 Windows)上的实现差异有哪些?

Go 调度器在不同操作系统(如 Linux 和 Windows)上的实现存在一些差异,主要体现在 I/O 多路复用机制和调度策略上。

  1. I/O 多路复用机制

    • Linux:Go 语言的运行时调度器在 Linux 上使用了 epoll 机制来实现 I/O 多路复用。epoll 是一种高效的事件驱动机制,能够处理大量并发的网络连接和 I/O 操作,从而提高系统的性能和响应速度。
    • Windows:在 Windows 上,Go 语言的运行时调度器使用了 IOCP(I/O Completion Port)机制。IOCP 是 Windows 特有的事件驱动机制,同样能够高效地处理大量并发的 I/O 操作。
  2. 调度策略

    • Linux:Linux 的进程调度器将线程作为最小调度单位,通过调度类的概念引入不同的调度策略来平衡低延时和实时性的问题。
    • Go 调度器:Go 调度器在用户层建立了新的模型,以 Goroutine 作为最小调度单位。这种设计使得 Go 调度器能够在用户态进行调度,减少了操作系统线程调度和上下文切换的开销。此外,Go 调度器还采用了工作窃取(Work Stealing)的方式,通过运行队列进行分区,平衡不同 CPU 或线程上的运行队列。
  3. 系统调用处理

    • Linux:Linux 系统调用通常由内核直接处理,而 Go 调度器需要通过用户态的机制来处理这些系统调用,例如使用 epoll 来处理网络请求和 I/O 操作。
    • Windows:在 Windows 上,Go 调度器使用 IOCP 来处理系统调用,这同样是一种高效的事件驱动机制,能够有效地处理大量并发的 I/O 操作。
  4. 调度器架构的演变

    • 早期 Go 调度器:最早期的 Go 调度器(Go 1 之前)甚至不能很好地支持多线程,最大 M 数为 1。这个版本的调度器在 Linux 上实现,但在其他操作系统上的支持并不完善。
    • 现代 Go 调度器:从 Go 1.1 起,引入了工作窃取调度器,大大提高了调度效率和并发性能。现代 Go 调度器在不同操作系统上都进行了优化,以适应各自的 I/O 多路复用机制和调度策略。

综上所述,Go 调度器在不同操作系统上的实现差异主要体现在 I/O 多路复用机制和调度策略上。Linux 上使用 epoll,而 Windows 上使用 IOCP

如何优化 Go 程序中的 Goroutine 使用以提高性能?

优化 Go 程序中的 Goroutine 使用以提高性能,可以从以下几个方面进行:

  1. 合理控制 Goroutine 数量

    • 过多的 Goroutine 会导致系统资源的过度消耗,甚至引发 Goroutine 泄露问题。因此,应根据实际需求合理控制 Goroutine 的数量,避免过度并发。
    • 使用 Goroutine 池化技术可以减少 Goroutine 的创建和销毁开销,从而提高性能。
  2. 优化 Channel 的使用

    • Channel 是 Goroutine 之间通信的主要方式,但 Channel 的传递大数据会带来值拷贝的开销。因此,尽量避免在 Channel 中传递大数据。
    • 使用 Channel 时,应尽量避免阻塞和死锁问题,确保 Channel 的使用高效且安全。
  3. 减少锁的使用

    • 锁是并发编程中常见的同步机制,但过度使用锁会导致性能下降。Go 推荐使用 Channel 的方式调用而不是共享内存,因为 Channel 之间存在大锁,可以降低锁的竞争力度。
    • 无锁编程通过原子操作减少锁的使用,可以进一步提升并发性能。
  4. 使用 Context 控制 Goroutine 生命周期

    • 设置超时和使用 context.WithTimeout 可以有效管理 Goroutine 的生命周期,避免 Goroutine 泄露。
  5. 减少系统调用

    • Goroutine 的实现是通过同步模拟异步操作,建议将同步调用隔离到可控 Goroutine 中,而不是直接高并发调用。
  6. 使用性能分析工具

    • 使用 Go 提供的性能分析工具如 pprof 和 trace,可以帮助检测 Goroutine 的运行时间、资源占用等,从而对 Goroutine 的创建和管理进行优化。
  7. 合理设置 GOMAXPROCS

    • GOMAXPROCS 控制了 Go 运行时可以使用的最大工作线程数。合理设置 GOMAXPROCS 可以提高并发性能。
  8. 避免内存泄漏

    • 在使用切片和映射时要合理设置容量,避免内存泄漏。
Goroutine 的抢占式调度机制具体是如何实现的?

Goroutine 的抢占式调度机制在 Go 语言中是通过多种方式实现的,主要包括基于协作的抢占和基于信号的抢占。以下是详细的实现机制:

  1. 基于协作的抢占

    • Goroutine 栈保护:每个 Goroutine 都有一个 stackguard0 字段,当该字段被设置为 StackPreempt 时,Goroutine 将被抢占。这个机制确保在函数调用时,调度器可以检查并触发抢占。
    • 抢占函数:Go 运行时引入了 preemptonepreemptall 函数,这些函数会设置 Goroutine 的 StackPreempt 标志,从而触发抢占。
    • 垃圾回收抢占:在垃圾回收阶段,运行时会调用 preemptall 函数设置所有处理器上 Goroutine 的 StackPreempt 标志,以确保垃圾回收期间所有 Goroutine 都能被抢占。
    • 长时间运行的 Goroutine 抢占:如果一个 Goroutine 的运行时间超过 10ms,系统监控线程(sysmon)会调用 retakepreemptone 函数进行抢占。
  2. 基于信号的抢占

    • 信号处理:在 Go 1.14 版本中,引入了基于信号的抢占机制。当系统处于特定状态(如 STW、执行 safe point 函数、sysmon 监控期间等)时,会向线程发送 SIGURG 信号。
    • 抢占处理函数:当线程收到 SIGURG 信号后,会调用 asyncPreempt 函数,将 asyncPreempt 的调用强制插入到用户当前执行的代码位置,从而实现真正的抢占。
  3. 系统调用引起的抢占

    • sysmon 线程监控:sysmon 线程负责监控系统资源和调度 Goroutine。当发现某个 Goroutine 执行系统调用时间过长时,会调用 retake 函数进行抢占。
    • 抢占逻辑:在抢占过程中,sysmon 会释放系统调用线程所绑定的 P(进程),而非阻止线程进行系统调用,而是合理利用资源。
  4. 调度器的抢占流程

    • 抢占时机:抢占式调度在以下场景下触发:Goroutine 执行时间过长、执行较长的函数调用链或栈帧扩展时。
    • 抢占行为:当 Goroutine 被标记为需要抢占时,调度器会将其放入全局可运行队列中,并更新其状态为 _Runnable,然后继续调度其他 Goroutine。

通过上述机制,Go 调度器能够确保在大部分情况下,不同的 Goroutine 都能获得均匀的时间片,提高了程序的可靠性和稳定性。

在高并发场景下,Goroutine 的调度策略对系统资源的利用效率有何影响?

在高并发场景下,Goroutine 的调度策略对系统资源的利用效率有显著影响。以下是详细的分析:

  1. 抢占式调度:Go 运行时系统实现了抢占式调度,以确保 Goroutine 公平地获得执行时间。如果一个 Goroutine 长时间占用 CPU(大约 10ms),运行时系统会抢占它,将其放回运行队列,并允许其他 Goroutine 执行。这种机制可以防止某个 Goroutine 占用过多资源,从而提高整体系统的资源利用率。

  2. 工作窃取算法:当一个 P(逻辑处理器)的本地运行队列为空时,它会尝试从其他 P 的本地运行队列中“偷取” Goroutine,以保持 CPU 的利用率。这种工作窃取算法避免了全局锁的使用,提高了调度效率,特别是在多核处理器上,可以更好地平衡负载。

  3. 优先级调度:Goroutine 的优先级是由 Go 运行时根据任务的优先级来设置的。优先级高的 Goroutine 会得到更多的资源分配,因此可以更快地执行。然而,在一些高并发场景下,大量请求 Goroutine 和其他工作 Goroutine 数量级差别较大,导致调度不合理,有时会导致调度不均衡。

  4. 资源分配:Goroutine 的资源分配是由 Go 运行时根据任务的资源需求来设置的,包括 CPU 时间片和内存空间等。在高并发场景下,频繁创建和销毁 Goroutine 可能会导致垃圾回收(GC)负担加重,影响系统响应速度。

  5. 锁竞争和优化:高频繁的锁竞争会导致性能瓶颈,特别是在高并发环境下。减少全局锁的使用和优化锁的策略可以提高系统的性能。

  6. 用户态调度:Goroutine 的调度在用户态进行,避免了内核态的资源争用和线程管理问题。这种机制使得 Goroutine 的切换更加高效,因为它们不依赖于操作系统提供的线程,而是通过用户态的切换实现。

  7. GOMAXPROCS 参数:GOMAXPROCS 参数决定了同时可执行的 Goroutine 数量,默认值为 CPU 核心数。调整 GOMAXPROCS 参数可以影响调度行为,从而优化资源利用效率。

  8. 内存管理:Goroutine 的栈大小是动态调整的,从 2KB 开始,最大可达 1GB。这种动态调整机制使得 Goroutine 能够更高效地利用内存资源,避免了固定大小栈带来的浪费。

综上所述,Goroutine 的调度策略在高并发场景下对系统资源的利用效率有显著影响。通过合理的调度策略和优化方法,可以提高系统的性能和资源利用率。

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

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

相关文章

xterm + vue3 + websocket 终端界面

xterm.js 下载插件 // xterm npm install --save xterm// xterm-addon-fit 使终端适应包含元素 npm install --save xterm-addon-fit// xterm-addon-attach 通过websocket附加到运行中的服务器进程 npm install --save xterm-addon-attach <template><div :…

记一次护网通过外网弱口令一路到内网

视频教程在我主页简介或专栏里 目录&#xff1a; 资产收集 前期打点 突破 完结 又是年底护网季&#xff0c;地市护网有玄机&#xff0c;一路磕磕又绊绊&#xff0c;终是不负领导盼。 扯远了-_-!!&#xff0c;年底来了一个地市级护网&#xff0c;开头挺顺利的&#xff0c…

XIAO ESP32 S3网络摄像头——2视频获取

本文主要是使用XIAO Esp32 S3制作网络摄像头的第2步,获取摄像头图像。 1、效果如下: 2、所需硬件 3、代码实现 3.1硬件代码: #include "WiFi.h" #include "WiFiClient.h" #include "esp_camera.h" #include "camera_pins.h"// 设…

uniapp:微信小程序文本长按无法出现复制菜单

一、问题描述 在集成腾讯TUI后&#xff0c;为了能让聊天文本可以复制&#xff0c;对消息组件的样式进行修改&#xff0c;主要是移除下面的user-select属性限制&#xff1a; user-select: none;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms…

2025:OpenAI的“七十二变”?

朋友们&#xff0c;准备好迎接AI的狂欢了吗&#xff1f;&#x1f680; 是不是跟我一样&#xff0c;每天醒来的第一件事就是看看AI领域又有什么新动向&#xff1f; 尤其是那个名字如雷贯耳的 OpenAI&#xff0c;简直就是AI界的弄潮儿&#xff0c;一举一动都牵动着我们这些“AI发…

无人机频射信号检测数据集,平均正确识别率在94.3%,支持yolo,coco json,pasical voc xml格式的标注,364张原始图片

无人机频射信号检测数据集&#xff0c;平均正确识别率在94.3&#xff05;&#xff0c;支持yolo&#xff0c;coco json&#xff0c;pasical voc xml格式的标注&#xff0c;364张原始图片 可识别下面的信号&#xff1a; 图像传输信号LFST &#xff08;Image_Transmission_sign…

柱状图中最大的矩形 - 困难

************* c topic: 84. 柱状图中最大的矩形 - 力扣&#xff08;LeetCode&#xff09; ************* chenck the topic first: Think about the topics I have done before. the rains project comes:盛最多水的容器 - 中等难度-CSDN博客https://blog.csdn.net/ElseWhe…

第17篇 使用数码管实现计数器___ARM汇编语言程序<四>

Q&#xff1a;如何使用定时器实现数码管循环计数器&#xff1f; A&#xff1a;DE1-SoC_Computer系统有许多硬件定时器&#xff0c;本次实验使用A9 Private Timer定时器实现延时&#xff1a;定时器首先向Load寄存器写入计数值&#xff0c;然后向Control寄存器中的使能位E写1来启…

SSM 进销存系统

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…

通过Cephadm工具搭建Ceph分布式存储以及通过文件系统形式进行挂载的步骤

1、什么是Ceph Ceph是一种开源、分布式存储系统&#xff0c;旨在提供卓越的性能、可靠性和可伸缩性。它是为了解决大规模数据存储问题而设计的&#xff0c;使得用户可以在无需特定硬件支持的前提下&#xff0c;通过普通的硬件设备来部署和管理存储解决方案。Ceph的灵活性和设计…

【Rust自学】8.4. String类型 Pt.2:字节、标量值、字形簇以及字符串的各类操作

8.4.0. 本章内容 第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构&#xff0c;这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。 第八章中的集合是存储在堆内存上而非栈内存上的&#xff0c;这也意味着这些集合的数据大小无需在编…

svn分支相关操作(小乌龟操作版)

在开发工作中进行分支开发&#xff0c;涉及新建分支&#xff0c;分支切换&#xff0c;合并分支等 新建远程分支 右键选择branch/tagert按钮 命名分支的路径名称 点击确定后远程分支就会生成一个当时命名的文件夹&#xff08;开发分支&#xff09; 分支切换 一般在开发阶段&a…

24年收尾之作------动态规划<六> 子序列问题(含对应LeetcodeOJ题)

目录 引例 经典LeetCode OJ题 1.第一题 2.第二题 3.第三题 4.第四题 5.第五题 6.第六题 7.第七题 引例 OJ传送门 LeetCode<300>最长递增子序列 画图分析: 使用动态规划解决 1.状态表示 dp[i]表示以i位置元素为结尾的子序列中&#xff0c;最长递增子序列的长度 2.…

蓝牙|软件 Qualcomm S7 Sound Platform开发系列之初级入门指南

本文适用范围 ADK24.2~ 问题/功能描述 S7开发环境搭建与编译介绍 实现方案 本文介绍适用于windows平台Application部分,audio ss的说明会在下一篇文章在做说明,Linux平台如果不进行AI算法的开发,个人认知是没有必要配置,若是做服务器倒是不错的选择.因为编译完成后烧录调试还…

Redis - 4 ( 9000 字 Redis 入门级教程 )

一&#xff1a; Zset 有序集合 1.1 常用命令 有序集合在 Redis 数据结构中相较于字符串、列表、哈希和集合稍显陌生。它继承了集合中元素不允许重复的特点&#xff0c;但与集合不同的是&#xff0c;有序集合的每个元素都关联一个唯一的浮点分数&#xff08;score&#xff09;…

ubuntu 使用samba与windows共享文件[注意权限配置]

在Ubuntu上使用Samba服务与Windows系统共享文件&#xff0c;需要正确配置Samba服务以及相应的权限。以下是详细的步骤&#xff1a; 安装Samba 首先&#xff0c;确保你的Ubuntu系统上安装了Samba服务。 sudo apt update sudo apt install samba配置Samba 安装完成后&#xff0c…

打印进度条

文章目录 1.Python语言实现(1)黑白色(2)彩色&#xff1a;蓝色 2.C语言实现(1)黑白颜色(2)彩色版&#xff1a;红绿色 1.Python语言实现 (1)黑白色 import sys import timedef progress_bar(percentage, width50):"""打印进度条:param percentage: 当前进度百分比…

深度解析 LDA 与聚类结合的文本主题分析实战

🌟作者简介:热爱数据分析,学习Python、Stata、SPSS等统计语言的小高同学~🍊个人主页:小高要坚强的博客🍓当前专栏:《Python之文本分析》🍎本文内容:深度解析 LDA 与聚类结合的文本主题分析实战🌸作者“三要”格言:要坚强、要努力、要学习 目录 引言 技术框架…

点跟踪基准最早的论文学习解读:TAP-Vid: A Benchmark for Tracking Any Point in a Video—前置基础

TAP-Vid: A Benchmark for Tracking Any Point in a Video— TAP-Vid&#xff1a;跟踪视频中任意点的基准、 学习这一篇文章的本来的目的是为了学习一下TAP-NET便于理解后面用到的TAPIR方法的使用。 文章目录 TAP-Vid: A Benchmark for Tracking Any Point in a Video— TAP-V…

C进阶-字符串与内存函数介绍(另加2道典型面试题)

满意的话&#xff0c;记得一键三连哦&#xff01; 我们先看2道面试题 第一道&#xff1a; 我们画图理解&#xff1a; pa&#xff0c;先使用再&#xff0c;pa开始指向a【0】&#xff0c;之后pa向下移动一位&#xff0c;再解引用&#xff0c;指向a【1】&#xff0c;a【1】又指向…