Go 调度器

这是一篇关于 Go 语言调度器的博客文章,主要介绍了 Go 1.24 版本在 Linux ARM 架构下的调度器实现,包括编译与 Go 运行时、原始调度器、调度器增强、GMP 模型、程序引导、创建协程、调度循环、查找可运行协程、协程抢占、处理系统调用、网络 I/O 和文件 I/O、netpoll 工作原理、垃圾回收器、通用函数和 Go 运行时 API 等方面。

主要观点

  • Go 语言的并发模型基于协程,调度器是编写高效并发程序的关键。
  • Go 调度器经历了从原始模型到 GMP 模型的演进,解决了早期调度器的一些问题。
  • GMP 模型中包含协程(g)、线程(m)和处理器(p)三种实体,各有其作用和状态机。
  • 调度器通过各种机制实现协程的创建、调度、抢占和系统调用处理等功能。
  • Go 利用非阻塞 I/O 和 I/O 多路复用技术高效处理 I/O 操作,netpoll 用于 I/O 多路复用。
  • Go 运行时提供了一些 API 供程序员与调度器和协程进行交互。

关键信息

  • 编译过程包括编译、汇编和链接三个阶段,Go 运行时是语言的核心,实现了调度、内存管理等功能。
  • 早期 Go 调度器采用 M:N 多线程模型,存在全局运行队列瓶颈、线程上下文切换开销大等问题。
  • 调度器增强提案包括引入本地运行队列和逻辑处理器,解决了早期问题,提高了调度器的性能。
  • GMP 模型中,协程表示执行单元,线程管理操作系统内核线程,处理器代表物理处理器。
  • 程序引导阶段初始化调度器,创建主协程和主线程,启动系统监控线程。
  • go关键字通过newproc函数创建协程,并将其放入运行队列。
  • 调度循环通过findRunnable函数查找可运行的协程,执行gogo函数开始协程执行。
  • 协程抢占分为非合作式抢占和合作式抢占,非合作式抢占由sysmon触发,合作式抢占基于栈保护机制。
  • 处理系统调用时,SyscallRawSyscall函数用于不同类型的系统调用,调度器在系统调用前后进行状态切换和处理。
  • Go 利用非阻塞 I/O 和 I/O 多路复用技术处理 I/O 操作,netpoll 用于抽象底层的 I/O 多路复用机制。
  • 垃圾回收器采用三色标记算法,在标记阶段和清扫阶段回收未使用的对象。
  • Go 运行时提供了GOMAXPROCSGoexit等 API 供程序员与调度器和协程进行交互。

重要细节

  • Go 运行时中的一些函数没有 Go 实现,而是由汇编代码实现,如getggogo等。
  • 本地运行队列和逻辑处理器的引入提高了调度器的性能,但仍存在一些问题,如内存消耗等。
  • 协程的状态机包括空闲、可运行、运行、系统调用、等待和死亡等状态,各状态之间可以相互转换。
  • 线程的状态机包括运行、系统调用、自旋和睡眠等状态,线程在不同状态下执行不同的操作。
  • 处理器的状态机包括空闲、运行、系统调用、GC 停止和死亡等状态,处理器在不同状态下与协程和线程进行交互。
  • 程序引导阶段创建的主协程在执行过程中可以被其他协程抢占,也可以等待系统调用或通道。
  • 创建协程时,会初始化协程的栈和goexit函数,将协程放入运行队列,并尝试唤醒其他处理器。
  • 调度循环中,找到可运行的协程后,线程通过gogo函数开始执行协程,协程执行完毕后会重新进入调度循环。
  • 协程抢占时,非合作式抢占由sysmon触发,通过发送信号强制抢占运行的协程;合作式抢占基于栈保护机制,当协程栈扩展时可能被抢占。
  • 处理系统调用时,调度器在系统调用前后进行状态切换,sysmon会监控系统调用的状态,进行处理器切换等操作。
  • Go 利用epoll等机制实现高效的 I/O 多路复用,netpoll 用于管理文件描述符的 I/O 事件。
  • 垃圾回收器在标记阶段和清扫阶段回收未使用的对象,期间可能会暂停程序。
  • GOMAXPROCS函数用于设置 Go 运行时的处理器数量,影响程序的并行度;Goexit函数用于优雅地终止当前协程。

总结来说,Go 调度器通过 GMP 模型和各种机制实现了高效的协程调度和并发处理,对于编写高效的 Go 程序具有重要意义。

阅读 35
0 条评论