goroutine

package
v0.0.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 28, 2026 License: MIT Imports: 10 Imported by: 0

README

goroutine

简介

goroutine 包提供了在 Go 程序中获取 goroutine ID 的功能和高效的协程池实现。虽然 Go 语言官方不推荐依赖 goroutine ID 进行业务逻辑处理,但在特定场景下(如调试、日志追踪、性能分析)获取 goroutine ID 非常有用。同时,包内提供了高性能的协程池实现,支持任务调度、资源管理和性能监控等功能。该包针对不同平台和架构提供了优化实现,确保高性能和广泛兼容性。

主要特性
  • 支持多种 CPU 架构(AMD64、ARM64)的优化实现
  • 提供通用的降级实现方案,确保所有平台兼容性
  • 优化的性能设计,针对不同平台特性进行调整
  • 简单易用的 API,便于快速集成
  • 完整的测试覆盖和基准测试
  • 高性能协程池实现,支持动态扩缩容
  • 丰富的配置选项,满足不同场景需求
  • 内置监控指标,便于性能分析和调优
设计理念

本包采用多层次实现策略,根据不同平台的特性提供最优性能的实现。设计核心是"优先性能,保证兼容",通过汇编语言和运行时结构直接访问等方式实现高效获取 goroutine ID。同时,包设计考虑了 Go 语言版本兼容性问题,针对不同版本的 Go 运行时结构提供了相应的适配。

协程池设计遵循以下原则:

  • 资源高效利用:动态调整协程数量,避免资源浪费
  • 任务调度:由底层 ants 协程池调度任务;如需严格顺序,应使用单 worker 或由调用方自行保证
  • 异常安全处理:内置 panic 恢复机制
  • 监控友好:提供丰富的运行时指标
  • 配置灵活:支持多种配置选项

安装

前置条件
  • 编译要求:请使用当前项目 go.mod 声明的 Go 版本构建和测试本包。
  • 运行时结构适配:goroutine ID 实现维护了 Go 1.5 起多个运行时版本的结构适配,用于兼容不同 Go 运行时布局。
  • 依赖要求:
    • goroutine ID 获取路径主要依赖 Go 标准库和平台相关汇编实现。
    • 协程池和指标能力依赖 github.com/panjf2000/ants/v2github.com/prometheus/client_golang 以及项目内日志包。
安装命令
go get -u github.com/fsyyft-go/kit/runtime/goroutine

快速开始

基础用法
获取 Goroutine ID
package main

import (
    "fmt"
    "github.com/fsyyft-go/kit/runtime/goroutine"
)

func main() {
    // 获取当前 goroutine 的 ID
    goid := goroutine.GetGoID()
    fmt.Printf("当前 goroutine ID: %d\n", goid)

    // 在多个 goroutine 中使用
    go func() {
        goid := goroutine.GetGoID()
        fmt.Printf("子 goroutine ID: %d\n", goid)
    }()
}
使用协程池
package main

import (
    "fmt"
    "sync"
    "time"
    "github.com/fsyyft-go/kit/runtime/goroutine"
)

func main() {
    // 创建协程池
    pool, cleanup, err := goroutine.NewGoroutinePool(
        goroutine.WithSize(10),           // 设置池大小
        goroutine.WithExpiry(time.Second), // 设置协程过期时间
        goroutine.WithName("worker"),      // 设置池名称
    )
    if err != nil {
        panic(err)
    }
    defer cleanup()

    // 提交任务
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        taskID := i
        err := pool.Submit(func() {
            defer wg.Done()
            fmt.Printf("执行任务 %d\n", taskID)
            time.Sleep(100 * time.Millisecond)
        })
        if err != nil {
            fmt.Printf("提交任务失败: %v\n", err)
        }
    }
    wg.Wait()

    // 查看池状态
    fmt.Printf("池容量: %d\n", pool.Cap())
    fmt.Printf("运行中协程: %d\n", pool.Running())
    fmt.Printf("空闲协程: %d\n", pool.Free())
    fmt.Printf("等待任务: %d\n", pool.Waiting())
}

详细指南

核心概念
Goroutine ID

goroutine ID 是 Go 运行时为每个 goroutine 分配的唯一标识符。虽然 Go 语言设计上不鼓励依赖 goroutine ID 进行编程,但在某些场景下(如调试、日志追踪)获取 goroutine ID 非常有价值。本包采用多种实现方式获取 goroutine ID:

  1. 快速路径:针对特定平台(AMD64、ARM64)的优化实现,直接访问运行时内部结构
  2. 慢速路径:通用实现,通过解析 goroutine 堆栈信息提取 ID
协程池

协程池是一种用于管理和复用 goroutine 的机制,主要功能包括:

  1. 资源管理:控制并发 goroutine 数量,避免资源耗尽
  2. 任务调度:由底层 ants 协程池调度任务并管理等待队列;不承诺严格执行顺序或公平性,如需严格顺序应使用单 worker 或由调用方保证
  3. 性能优化:动态调整协程数量,优化资源利用
  4. 监控统计:提供运行时指标,便于性能分析
配置选项

协程池支持丰富的配置选项,可以通过 NewGoroutinePool 函数的选项参数进行配置:

// 创建协程池示例
pool, cleanup, err := goroutine.NewGoroutinePool(
    goroutine.WithSize(100),              // 设置池大小
    goroutine.WithExpiry(time.Second),    // 设置协程过期时间
    goroutine.WithPreAlloc(true),         // 预创建协程
    goroutine.WithNonBlocking(false),     // 阻塞模式
    goroutine.WithMaxBlocking(1000),      // 最大阻塞任务数
    goroutine.WithPanicHandler(func(r interface{}) {
        // 处理 panic
    }),
    goroutine.WithName("worker"),         // 设置池名称
    goroutine.WithMetrics(true),          // 启用指标收集
)

主要配置选项说明:

  • WithSize:设置协程池大小
  • WithExpiry:设置协程过期时间
  • WithPreAlloc:是否预创建协程
  • WithNonBlocking:是否使用非阻塞模式
  • WithMaxBlocking:最大阻塞任务数
  • WithPanicHandler:panic 处理函数
  • WithName:协程池名称
  • WithMetrics:是否启用指标收集
常见用例
1. 在日志系统中跟踪 goroutine
package main

import (
    "fmt"
    "log"
    "sync"
    "github.com/fsyyft-go/kit/runtime/goroutine"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(taskID int) {
            defer wg.Done()
            goid := goroutine.GetGoID()
            log.Printf("[goroutine:%d] 执行任务 %d", goid, taskID)
            // 执行业务逻辑...
        }(i)
    }
    wg.Wait()
}
2. 使用协程池处理并发任务
package main

import (
    "fmt"
    "sync"
    "time"
    "github.com/fsyyft-go/kit/runtime/goroutine"
)

func main() {
    // 创建协程池
    pool, cleanup, err := goroutine.NewGoroutinePool(
        goroutine.WithSize(10),
        goroutine.WithName("worker"),
    )
    if err != nil {
        panic(err)
    }
    defer cleanup()

    // 提交并发任务
    var wg sync.WaitGroup
    for i := 0; i < 20; i++ {
        wg.Add(1)
        taskID := i
        err := pool.Submit(func() {
            defer wg.Done()
            fmt.Printf("任务 %d 开始执行\n", taskID)
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("任务 %d 执行完成\n", taskID)
        })
        if err != nil {
            fmt.Printf("提交任务 %d 失败: %v\n", taskID, err)
            wg.Done()
        }
    }
    wg.Wait()
}
最佳实践
Goroutine ID 使用建议
  • 谨慎依赖 goroutine ID,不要将其作为业务逻辑的核心
  • 在性能敏感场景,获取 ID 后应缓存使用,避免重复获取
  • 在适当的抽象层次使用 goroutine ID,如日志系统、调试工具
  • 避免使用 goroutine ID 作为同步或通信机制的依赖
  • 在不支持快速路径的平台上,注意性能损耗问题
协程池使用建议
  • 根据实际负载合理设置池大小,避免资源浪费
  • 使用非阻塞模式时注意处理任务提交失败的情况
  • 合理设置协程过期时间,平衡资源利用和响应速度
  • 在关键任务中实现 panic 处理,确保系统稳定性
  • 定期监控池状态,及时发现性能问题
  • 使用池名称区分不同业务场景的协程池
  • 在服务关闭时正确清理协程池资源

API 文档

主要类型
// GoroutinePool 定义了协程池的接口
type GoroutinePool interface {
    // Submit 提交任务到协程池
    Submit(task func()) error
    // Tune 调整协程池大小
    Tune(size int)
    // Cap 获取协程池容量
    Cap() int
    // Running 获取运行中协程数
    Running() int
    // Free 获取空闲协程数
    Free() int
    // Waiting 获取等待任务数
    Waiting() int
    // IsClosed 检查协程池是否已关闭
    IsClosed() bool
}
关键函数
GetGoID

获取当前 goroutine 的 ID。根据平台和架构自动选择最优实现。

func GetGoID() int64

示例:

id := goroutine.GetGoID()
fmt.Printf("当前 goroutine ID: %d\n", id)
GetGoIDSlow

获取当前 goroutine 的 ID,使用通用但较慢的实现方式。适用于所有平台。

func GetGoIDSlow() int64

示例:

id := goroutine.GetGoIDSlow()
fmt.Printf("使用慢速路径获取的 goroutine ID: %d\n", id)
NewGoroutinePool

创建新的协程池实例。

func NewGoroutinePool(opts ...Option) (GoroutinePool, func(), error)

示例:

pool, cleanup, err := goroutine.NewGoroutinePool(
    goroutine.WithSize(10),
    goroutine.WithName("worker"),
)
if err != nil {
    panic(err)
}
defer cleanup()
Submit

提交任务到默认协程池。

func Submit(task func()) error

示例:

err := goroutine.Submit(func() {
    // 执行任务
})
if err != nil {
    // 处理错误
}
错误处理

本包的协程池创建和提交函数会透传底层 github.com/panjf2000/ants/v2 返回的错误,例如池已关闭、池过载或配置无效等场景。runtime/goroutine 包自身不导出 ErrPoolClosedErrPoolOverload 等错误变量;如需精确匹配错误类型,请直接参考并使用 ants/v2 的错误定义。

建议在关键应用中添加适当的错误处理:

pool, cleanup, err := goroutine.NewGoroutinePool(
    goroutine.WithSize(10),
)
if err != nil {
    // 处理创建失败
    log.Printf("创建协程池失败: %v", err)
    return
}
defer cleanup()

err = pool.Submit(func() {
    // 执行任务
})
if err != nil {
    // 处理提交失败
    log.Printf("提交任务失败: %v", err)
    return
}

性能指标

操作 性能指标 说明
GetGoID (AMD64) ~5ns/op 在 AMD64 架构上,通过汇编优化,接近直接内存访问
GetGoID (ARM64) ~8ns/op 在 ARM64 架构上,通过直接访问 g 结构体
GetGoIDSlow ~200ns/op 通过解析堆栈信息,性能较低但通用性好
任务提交 ~100ns/op 提交任务到协程池的开销
协程创建 ~1μs/op 创建新协程的开销
任务调度 ~50ns/op 任务调度的开销

测试覆盖率

当前单元测试目标为包级覆盖率 95% 以上。请以 go test ./runtime/goroutine -cover -count=1 的实时结果为准,避免文档中的固定数值与代码演进脱节。

调试指南

日志行为

包级 Submit 会在任务发生 panic 时恢复该 panic,并通过项目内日志包记录错误日志。goroutine ID 获取、平台降级、包初始化、版本适配和性能数据路径当前不产生独立的 WARN、INFO 或 DEBUG 日志。

常见问题排查
在 M1/M2 芯片的 Mac 设备上获取的 ID 不稳定

在 Darwin ARM64 架构(如 M1/M2 Mac)上,由于平台限制,可能需要使用不同的实现。请确保使用最新版本的包。

不同 Go 版本表现不一致

本包针对不同 Go 版本的运行时结构提供了适配。如果在特定 Go 版本上遇到问题,请检查是否使用了匹配的适配文件。

相关文档

贡献指南

我们欢迎任何形式的贡献,包括但不限于:

  • 报告问题
  • 提交功能建议
  • 提交代码改进
  • 完善文档

请参考我们的贡献指南了解详细信息。

许可证

本项目采用 MIT 许可证。查看 LICENSE 文件了解更多信息。

Documentation

Overview

Package goroutine 提供 goroutine ID 读取工具和基于 ants 的协程池封装。

GetGoID 会按当前架构和 Go 版本选择快速路径;在未提供快速路径的平台上会退回到基于 runtime.Stack 的慢速解析实现,调用方也可显式使用 GetGoIDSlow。amd64 构建下, Offset 返回快速路径使用的 runtime.g.goid 字段偏移。NewGoroutinePool 用于创建独立 协程池实例,返回的 cleanup 负责停止指标采集协程并释放底层 ants.Pool 资源;包级 Submit 会惰性创建并复用默认池,在任务 panic 时 recover 并记录日志,不会把 panic 继续向调用方传播。

本包的快速路径依赖 runtime 内部结构、汇编实现和按 Go 版本维护的偏移信息;升级 Go 版本或切换目标架构后需要重新验证对应实现。

Index

Constants

This section is empty.

Variables

View Source
var (
	// MetricWorkerCurrent 记录协程池的当前状态。
	//
	// 标签:
	//   - name:协程池名称,对应 WithName 配置。
	//   - state:指标维度,可选值包括:
	//     - cap:协程池容量,对应 GoroutinePool.Cap。
	//     - running:正在执行任务的 worker 数量,对应 GoroutinePool.Running。
	//     - free:空闲 worker 数量,对应 GoroutinePool.Free。
	//     - waiting:等待调度的任务数量,对应 GoroutinePool.Waiting。
	MetricWorkerCurrent = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: namespace,
		Subsystem: subsystem,
		Name:      "current",
		Help:      "goroutine pool's worker current.",
	}, []string{"name", "state"})
)

Functions

func GetGoID

func GetGoID() int64

GetGoID 返回当前 goroutine 的 ID。

该快速路径通过 goid_amd64.s 直接读取当前 runtime.g 的 goid 字段。 SAFETY: 此实现依赖 Offset 返回的偏移值与目标 Go 版本的 runtime.g 布局保持一致; 升级 Go 版本后需要同步校验偏移表、汇编代码和配套结构定义。

参数:无。

返回:

  • int64:当前 goroutine 的 ID。

func GetGoIDSlow

func GetGoIDSlow() int64

GetGoIDSlow 通过慢速解析路径返回当前 goroutine 的 ID。

该函数始终使用 runtime.Stack 解析首行文本提取 goroutine ID,不依赖架构专用 汇编快速路径,适合在需要显式回退行为时使用。

参数:无。

返回:

  • int64:当前 goroutine 的 ID。

func Offset

func Offset() int64

Offset 返回 amd64 快速路径当前使用的 runtime.g.goid 字段偏移量。

返回 0 通常表示当前 Go 版本尚未写入 offsetDict。

参数:无。

返回:

  • int64:当前 Go 版本对应的 runtime.g.goid 字段偏移量;未命中 offsetDict 时返回 0。

func Submit added in v0.0.10

func Submit(task func()) error

Submit 将 task 提交到包级默认协程池执行。

首次调用会惰性创建默认池。与显式 GoroutinePool.Submit 不同,包级包装层会 recover task panic 并记录日志, 不会把 panic 继续向调用方传播,也不会通过返回值暴露该 panic。

参数:

  • task:要提交到包级默认协程池执行的任务函数。

返回:

  • error:默认池初始化失败或底层提交失败时返回错误。task panic 会被 recover 并记录日志,不通过返回值暴露。

Types

type GoroutinePool added in v0.0.10

type GoroutinePool interface {
	// Submit 提交一个任务到协程池中异步执行。
	//
	// 参数:
	//   - task:要交给协程池执行的任务函数。
	//
	// 返回:
	//   - error:底层协程池关闭或拒绝接收任务时返回错误。
	Submit(task func()) error

	// Tune 调整协程池的容量。
	//
	// 参数:
	//   - size:新的协程池容量。
	Tune(size int)

	// Cap 返回协程池当前容量。
	//
	// 参数:无。
	//
	// 返回:
	//   - int:协程池当前容量。
	Cap() int

	// Running 返回当前正在执行任务的 worker 数量。
	//
	// 参数:无。
	//
	// 返回:
	//   - int:当前正在执行任务的 worker 数量。
	Running() int

	// Free 返回当前空闲的 worker 数量。
	//
	// 参数:无。
	//
	// 返回:
	//   - int:当前空闲的 worker 数量。
	Free() int

	// Waiting 返回当前等待调度的任务数量。
	//
	// 参数:无。
	//
	// 返回:
	//   - int:当前等待调度的任务数量。
	Waiting() int

	// IsClosed 报告协程池是否已经关闭。
	//
	// 参数:无。
	//
	// 返回:
	//   - bool:协程池已关闭时返回 true。
	IsClosed() bool
}

GoroutinePool 定义协程池的任务提交、容量调整和状态查询能力。

该接口由 NewGoroutinePool 返回的实例实现。

func NewGoroutinePool added in v0.0.10

func NewGoroutinePool(opts ...Option) (GoroutinePool, func(), error)

NewGoroutinePool 创建一个新的协程池实例。

参数:

  • opts:可选配置项,按传入顺序覆盖默认配置。

返回:

  • GoroutinePool:创建成功的协程池实例。
  • func():cleanup 函数;调用方在不再使用该实例时应调用一次,用于停止指标采集协程并释放底层 ants.Pool 资源。
  • error:底层 ants.NewPool 创建失败时返回错误;此时协程池实例和 cleanup 都为 nil。

type Option added in v0.0.10

type Option func(p *goroutinePool)

Option 定义协程池配置修改函数。

参数:

  • p:待修改的协程池配置实例。

func WithExpiry added in v0.0.10

func WithExpiry(expiry time.Duration) Option

WithExpiry 设置空闲 worker 的回收周期。

参数:

  • expiry:空闲 worker 在底层池中保留的最长空闲时间。

返回:

  • Option:用于更新 worker 回收周期的选项函数。

func WithMaxBlocking added in v0.0.10

func WithMaxBlocking(maxBlocking int) Option

WithMaxBlocking 设置阻塞提交模式下允许等待的最大任务数。

参数:

  • maxBlocking:传给 ants.WithMaxBlockingTasks 的最大等待任务数。

返回:

  • Option:用于更新最大阻塞任务数的选项函数。

func WithMetrics added in v0.0.10

func WithMetrics(metrics bool) Option

WithMetrics 设置是否启用指标采集。

参数:

  • metrics:为 true 时启动后台采集协程并持续更新 MetricWorkerCurrent。

返回:

  • Option:用于更新指标采集开关的选项函数。

func WithName added in v0.0.10

func WithName(name string) Option

WithName 设置协程池实例名称。

参数:

  • name:写入指标标签的协程池名称;为空时指标使用空名称标签。

返回:

  • Option:用于更新协程池名称的选项函数。

func WithNonBlocking added in v0.0.10

func WithNonBlocking(nonBlocking bool) Option

WithNonBlocking 设置是否启用非阻塞提交模式。

参数:

  • nonBlocking:为 true 时在池满后立即返回提交错误,而不是等待空闲 worker。

返回:

  • Option:用于更新提交阻塞策略的选项函数。

func WithPanicHandler added in v0.0.10

func WithPanicHandler(panicHandler func(interface{})) Option

WithPanicHandler 设置底层协程池的 panic 回调。

参数:

  • panicHandler:传给 ants.WithPanicHandler 的回调函数,用于处理 worker 执行任务时发生的 panic。

返回:

  • Option:用于更新 panic 回调的选项函数。

func WithPreAlloc added in v0.0.10

func WithPreAlloc(preAlloc bool) Option

WithPreAlloc 设置是否在初始化时预分配 worker。

参数:

  • preAlloc:为 true 时在创建协程池时预分配 worker。

返回:

  • Option:用于更新预分配策略的选项函数。

func WithSize added in v0.0.10

func WithSize(size int) Option

WithSize 设置协程池容量。

参数:

  • size:传给底层 ants.NewPool 的容量值。

返回:

  • Option:用于更新协程池容量配置的选项函数。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL