浅析Golang中调度器的关键机制与性能

 更新时间:2025年03月05日 10:09:31   作者:Ai 编码  
Golang的调度器是其并发模型的核心组件,负责管理Goroutine的调度和执行,本文将从理论和代码层面分析Golang调度器的关键机制,感兴趣的可以了解下

Golang的调度器是其并发模型的核心组件,负责管理Goroutine的调度和执行。Goroutine是Go语言中的轻量级线程,由Go运行时(runtime)管理。

调度器的设计目标是高效地利用多核CPU,同时保持低延迟和高吞吐量。下面我们从理论和代码层面分析Golang调度器的关键机制。

1. 调度器的基本概念

1.1 Goroutine

Goroutine是Go语言中的并发执行单元,比操作系统线程更轻量。每个Goroutine初始时只占用几KB的栈空间,栈空间可以根据需要动态增长或缩减。

1.2 M-P-G 模型

Go调度器采用了M-P-G模型:

  • M (Machine): 代表操作系统线程(OS Thread),由操作系统调度。
  • P (Processor): 代表逻辑处理器,负责调度Goroutine。P的数量通常等于CPU核心数,可以通过GOMAXPROCS设置。
  • G (Goroutine): 代表Go的并发执行单元。

2. 调度器的核心机制

2.1 工作窃取(Work Stealing)

当一个P的本地队列中没有可运行的Goroutine时,它会尝试从其他P的队列中“窃取”Goroutine来执行。这种机制可以平衡各个P的负载,避免某些P空闲而其他P过载。

2.2 抢占式调度

Go调度器是抢占式的,意味着它可以在Goroutine执行过程中强制切换执行权。Go 1.14引入了基于信号的抢占机制,确保长时间运行的Goroutine不会阻塞其他Goroutine的执行。

2.3 系统调用

当Goroutine执行系统调用时,调度器会将当前的M与P分离,并创建一个新的M来执行系统调用。这样可以避免系统调用阻塞整个P的执行。

3. 代码解析

3.1 调度器的初始化

调度器的初始化在runtime/proc.go中的schedinit函数中完成。该函数设置了P的数量、初始化全局队列等。

func schedinit() {
    // 初始化P的数量
    procs := int(ncpu)
    if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
        procs = n
    }
    if procresize(procs) != nil {
        throw("unknown runnable goroutine during bootstrap")
    }
}

3.2 Goroutine的创建与调度

Goroutine的创建通过go关键字触发,最终会调用runtime.newproc函数。该函数会将新的Goroutine放入当前P的本地队列中。

func newproc(siz int32, fn *funcval) {
    argp := add(unsafe.Pointer(&fn), sys.PtrSize)
    gp := getg()
    pc := getcallerpc()
    systemstack(func() {
        newg := newproc1(fn, argp, siz, gp, pc)
        _p_ := getg().m.p.ptr()
        runqput(_p_, newg, true)
        if mainStarted {
            wakep()
        }
    })
}

3.3 调度循环

调度器的核心是调度循环,位于runtime/proc.go中的schedule函数。该函数会从当前P的本地队列、全局队列或其他P的队列中获取可运行的Goroutine并执行。

func schedule() {
    _g_ := getg()

    if _g_.m.locks != 0 {
        throw("schedule: holding locks")
    }

    if _g_.m.lockedg != 0 {
        stoplockedm()
        execute(_g_.m.lockedg.ptr(), false) // Never returns.
    }

    // 调度循环
    for {
        if sched.gcwaiting != 0 {
            gcstopm()
            continue
        }
        if _g_.m.p.ptr().runqempty() {
            // 如果本地队列为空,尝试从全局队列或其他P窃取Goroutine
            gp, inheritTime = runqget(_g_.m.p.ptr())
            if gp == nil {
                gp, inheritTime = findrunnable() // 阻塞直到找到可运行的Goroutine
            }
        } else {
            // 从本地队列获取Goroutine
            gp, inheritTime = runqget(_g_.m.p.ptr())
        }
        execute(gp, inheritTime)
    }
}

3.4 抢占机制

Go 1.14引入了基于信号的抢占机制,确保长时间运行的Goroutine不会阻塞其他Goroutine的执行。抢占机制的实现位于runtime/signal_unix.go中。

func preemptM(mp *m) {
    if atomic.Cas(&mp.signalPending, 0, 1) {
        signalM(mp, sigPreempt)
    }
}

4. 性能与并发

4.1 高效利用多核

通过M-P-G模型,Go调度器能够高效地利用多核CPU。每个P绑定到一个M上,M是操作系统线程,P负责调度Goroutine。P的数量通常等于CPU核心数,这样可以最大限度地利用CPU资源。

4.2 低延迟

Go调度器的抢占式调度和基于信号的抢占机制确保了低延迟。即使某个Goroutine长时间运行,调度器也能及时切换执行权,避免其他Goroutine被长时间阻塞。

4.3 高吞吐量

工作窃取机制确保了各个P之间的负载均衡,避免了某些P过载而其他P空闲的情况。这种机制提高了系统的整体吞吐量。

5. 总结

Golang的调度器通过M-P-G模型、工作窃取、抢占式调度等机制,实现了高效的并发和并行执行。调度器的设计使得Go语言在处理高并发场景时表现出色,能够充分利用多核CPU资源,同时保持低延迟和高吞吐量。

通过深入理解调度器的工作原理,开发者可以更好地编写高效的并发程序,充分利用Go语言的并发特性。

到此这篇关于浅析Golang中调度器的关键机制与性能的文章就介绍到这了,更多相关Golang调度器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于golang时间转换的问题

    基于golang时间转换的问题

    下面小编就为大家带来一篇基于golang时间转换的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • golang实现的文件上传下载小工具

    golang实现的文件上传下载小工具

    这篇文章主要介绍了golang实现的文件上传下载小工具,帮助大家更好的理解和使用python,感兴趣的朋友可以了解下
    2020-12-12
  • Go写一个飞书机器人定时发送消息的项目实践

    Go写一个飞书机器人定时发送消息的项目实践

    本文主要介绍了Go写一个飞书机器人定时发送消息的项目实践,需配置Webhook地址并确保机器人在群聊中,代码每分钟触发一次,发送当前Unix时间戳消息,控制台记录调用日志与HTTP状态码
    2025-06-06
  • 详解在Go语言单元测试中如何解决Redis存储依赖问题

    详解在Go语言单元测试中如何解决Redis存储依赖问题

    在编写单元测试时,除了 MySQL 这个外部存储依赖,Redis 应该是另一个最为常见的外部存储依赖了,本文就来讲解下如何解决 Redis 外部依赖,文章通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-08-08
  • golang image图片处理示例

    golang image图片处理示例

    这篇文章主要介绍了golang image图片处理的方法,结合实例形式分析了Go语言针对图片的打开、读取、转换等相关操作技巧,需要的朋友可以参考下
    2016-07-07
  • Go语言实现支付宝支付与退款详解

    Go语言实现支付宝支付与退款详解

    本文详细介绍使用Go语言对接支付宝支付与退款功能的步骤和注意事项,包括PC端、WAP端和Android端支付实现,以及退款功能的代码实现,介绍了GoPay库的使用,帮助开发者快速集成支付宝支付到应用中
    2024-10-10
  • 详解golang碎片整理之 fmt.Scan

    详解golang碎片整理之 fmt.Scan

    本文介绍了从golang语言中fmt包从标准输入获取数据的Scan系列函数、从io.Reader中获取数据的Fscan系列函数以及从字符串中获取数据的Sscan系列函数的用法,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • Go语言中Pool对象复用的实现

    Go语言中Pool对象复用的实现

    本文主要介绍了Go语言中Pool对象复用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-04-04
  • go语言中数据接口set集合的实现

    go语言中数据接口set集合的实现

    set集合是一种常见的数据结构,它代表了一个唯一元素的集合,本文主要介绍了set的基本特性,包括唯一性、无序性、可变性和集合运算,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-10-10
  • Go读取yaml文件到struct类的实现方法

    Go读取yaml文件到struct类的实现方法

    本文主要介绍了Go读取yaml文件到struct类,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01

最新评论