找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2442

积分

0

好友

343

主题
发表于 15 小时前 | 查看: 1| 回复: 0

Go 1.26 即将引入一个关于运行时监控的重磅特性——Goroutine 调度指标。

这个特性看似不起眼,但对生产环境的可观测性来说至关重要,值得开发者关注和升级。

背景

说起 Go 的 runtime/metrics 包,相信做过性能调优的同学都不陌生。这个包从 Go 1.16 开始提供各种运行时的统计数据,例如内存分配、GC 情况等。

然而,一个令人无奈的事实是,这么多年过去了,关于 Goroutine 调度器的 metrics 指标却一直是空白的。

Go runtime/metrics 包文档截图

一些痛点

我们来看看实际开发中可能遇到的痛点。

假设线上服务突然变慢,打开 Grafana 监控面板却发现:

  • CPU 使用率正常。
  • 内存占用也无问题。
  • GC 次数和耗时均在合理范围内。

问题究竟出在哪里?

很可能是 Goroutine 调度层面出了问题,从而引发性能异常。但过去你无法直接观测到以下关键信息:

  • 程序到底创建了多少个 Goroutine?
  • 有多少 Goroutine 在排队等待执行?
  • 有没有 Goroutine 卡在系统调用或 cgo 调用中出不来?
  • 运行时创建的线程数是否异常增长?

“新” 提案

这种无法观测的状态相当尴尬。实际上,早在 2016 年,就有开发者在 GitHub issue #15490 中提出了这个需求。

GitHub issue #15490 提案截图

提案的描述很直接:“MemStats 提供了一种监控内存分配和垃圾回收的方式,我们也需要一个类似的工具来监控调度器。”

简单来说,当时的诉求就是需要能观测到:

  • 程序启动以来总共创建了多少 Goroutine。
  • 当前存活的 Goroutine 数量。
  • 运行时启动了多少线程。
  • Goroutine 从就绪状态到实际运行的延迟情况。

这个提案一提就是近十年,如今 Go 核心团队终于决定在 Go 1.26 版本中将其落地实现。

新的 Goroutine 调度指标

那么,Go 1.26 具体新增了哪些调度相关的指标呢?

runtime/metrics 包中,新增了以下 6 个关于 Goroutine 和线程的核心指标:

  1. /sched/goroutines-created:goroutines:从程序启动到现在总共创建的 Goroutine 数量。
  2. /sched/goroutines/not-in-go:goroutines:当前处于系统调用或 cgo 调用中的 Goroutine 数量(近似值)。
  3. /sched/goroutines/runnable:goroutines:当前已就绪但尚未被调度执行的 Goroutine 数量(近似值)。
  4. /sched/goroutines/running:goroutines:当前正在执行的 Goroutine 数量(近似值)。
  5. /sched/goroutines/waiting:goroutines:当前正在等待某个资源(如 I/O、channel、锁)的 Goroutine 数量(近似值)。
  6. /sched/threads/total:threads:当前 Go runtime 拥有的活跃线程总数。

这里有几点需要特别注意:

  1. 近似值说明:按状态统计的 Goroutine 数量(not-in-gorunnablerunningwaiting)都被标记为“近似值”。这是因为 Goroutine 的状态切换极其频繁,runtime 不可能为了获取一个绝对精确的瞬时值而施加全局锁,那会严重损害性能。对于监控和趋势分析而言,这些近似值已经足够有效。
  2. 总和不一定等于总数:上述分类状态的数量相加,不一定等于当前存活的 Goroutine 总数(即 /sched/goroutines:goroutines 指标的值)。这同样是由于采样时机和并发性导致。
  3. 数据类型:所有新增指标均使用 uint64 类型的计数器。

使用示例

当 Go 1.26 发布后,我们可以这样使用这些新指标。假设我们有一个简单的程序,启动了若干 Goroutine 来模拟不同的工作任务:

package main

import (
    "fmt"
    "runtime/metrics"
    "time"
)

func main() {
    // 启动一些 Goroutine 模拟真实场景
    go work()

    // 等待 100ms 让 Goroutine 运行一段时间
    time.Sleep(100 * time.Millisecond)

    // 打印 Goroutine 相关指标
    fmt.Println("Goroutine 调度指标:")
    printMetric("/sched/goroutines-created:goroutines", "累计创建")
    printMetric("/sched/goroutines:goroutines", "当前存活")
    printMetric("/sched/goroutines/not-in-go:goroutines", "系统调用/CGO")
    printMetric("/sched/goroutines/runnable:goroutines", "等待执行")
    printMetric("/sched/goroutines/running:goroutines", "正在执行")
    printMetric("/sched/goroutines/waiting:goroutines", "等待资源")

    // 打印线程相关指标
    fmt.Println("\n线程指标:")
    printMetric("/sched/gomaxprocs:threads", "最大 P 数量")
    printMetric("/sched/threads/total:threads", "当前线程数")
}

func printMetric(name string, descr string) {
    sample := []metrics.Sample{{Name: name}}
    metrics.Read(sample)
    // 注意:此处为演示简化了错误处理
    // 生产代码中应检查 sample[0].Value.Kind()
    fmt.Printf("  %s: %v\n", descr, sample[0].Value.Uint64())
}

func work() {
    // 此处省略具体的工作逻辑,例如发起网络请求、执行计算任务等
}

运行上述程序,输出可能类似于:

Goroutine 调度指标:
  累计创建: 52
  当前存活: 12
  系统调用/CGO: 0
  等待执行: 0
  正在执行: 4
  等待资源: 8

线程指标:
  最大 P 数量: 8
  当前线程数: 4

从输出可以清晰地看到:程序累计创建了 52 个 Goroutine,当前存活 12 个。其中,4 个正在执行,8 个在等待资源(可能是在等待 channel、锁或 I/O 操作),没有 Goroutine 卡在系统调用中,也没有 Goroutine 在就绪队列中排队。

读取这些新指标的方式与使用现有的 runtime/metrics 包完全一致,都是通过 metrics.Read 函数来获取,因此在集成到现有监控系统中时基本没有额外的学习成本。

总结

这个从 2016 年提出的提案,社区呼声一直很高。Go 核心团队成员 Michael Knyszek 最终在 2024 年底推动了这个特性的实现,相关的代码变更已提交。

Go1.26 里程碑状态截图

尽管新增的 API 非常简单,但其对生产环境可观测性的意义却非常重大。有了这些指标,我们终于能够直接洞察 Go 程序内部 Goroutine 的调度状态,而不再需要基于间接现象进行猜测和排查。

等 Go 1.26 正式发布后,建议开发者可以第一时间将这些新指标集成到监控系统中,相信它们对于定位和解决生产环境下的性能瓶颈、资源竞争等问题会有不小的帮助。如果你想了解更多关于 Go 调度模型或其他底层原理,欢迎到 云栈社区 的 Go 技术板块进行深入探讨和交流。




上一篇:Node.js文件操作:使用fs.promises与async/await替代回调
下一篇:Python实用工具库PyDash入门指南:简化列表与字典的日常数据处理
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-18 18:14 , Processed in 0.503218 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表