在Golang的并发模型中,协程调度器负责管理大量goroutine的生命周期与执行顺序,原生调度器采用M:N的调度模型,将用户态的goroutine映射到内核态的线程上执行。理解调度器的实现逻辑,有助于我们更深入掌握Golang的并发特性,也可以根据业务需求实现定制化的轻量调度器。

原生Golang协程调度器核心模型
原生调度器主要包含三个核心角色,分别是G、M、P:
- G(Goroutine):代表一个协程,存储协程的执行栈、状态、函数入口等信息
- M(Machine):代表内核线程,是真正执行代码的实体,一个M在同一时间只能绑定一个P
- P(Processor):代表调度上下文,存储本地可运行的G队列,数量默认等于GOMAXPROCS的值
调度的基本流程是P从本地队列或者全局队列中获取可运行的G,绑定到M上执行,当G发生阻塞或者需要让出CPU时,M会解绑P,P会重新寻找其他可运行的G绑定执行。
简易协程调度器实现思路
我们可以简化原生模型,实现一个仅包含G和调度队列的轻量调度器,核心功能包括协程创建、入队、调度执行、状态管理。首先定义协程的结构体,包含协程ID、执行函数、当前状态:
package main
import (
"fmt"
"sync"
"time"
)
// 协程状态常量
const (
GoroutineReady = iota // 就绪状态,可调度
GoroutineRunning // 运行状态
GoroutineWaiting // 等待状态,比如阻塞
GoroutineDead // 结束状态
)
// Goroutine 定义协程结构体
type Goroutine struct {
ID int
Fn func() // 协程执行的函数
Status int // 协程当前状态
}
调度队列设计
调度队列用于存储就绪状态的协程,我们使用无锁队列简化实现,同时维护一个全局的协程ID计数器:
// Scheduler 调度器结构体
type Scheduler struct {
queue []*Goroutine // 就绪队列
mu sync.Mutex // 保护队列的锁
nextID int // 下一个协程ID
}
核心方法实现
调度器需要实现协程创建、协程入队、调度执行三个核心方法:
// NewScheduler 创建调度器实例
func NewScheduler() *Scheduler {
return &Scheduler{
queue: make([]*Goroutine, 0),
nextID: 1,
}
}
// Go 创建新的协程并加入就绪队列
func (s *Scheduler) Go(fn func()) int {
s.mu.Lock()
defer s.mu.Unlock()
g := &Goroutine{
ID: s.nextID,
Fn: fn,
Status: GoroutineReady,
}
s.nextID++
s.queue = append(s.queue, g)
fmt.Printf("创建协程 ID: %dn", g.ID)
return g.ID
}
// schedule 调度执行就绪队列中的协程
func (s *Scheduler) schedule() {
for {
s.mu.Lock()
if len(s.queue) == 0 {
s.mu.Unlock()
time.Sleep(10 * time.Millisecond)
continue
}
// 取出队首的就绪协程
g := s.queue[0]
s.queue = s.queue[1:]
s.mu.Unlock()
// 执行协程函数
g.Status = GoroutineRunning
fmt.Printf("开始执行协程 ID: %dn", g.ID)
go func(goroutine *Goroutine) {
defer func() {
goroutine.Status = GoroutineDead
fmt.Printf("协程 ID: %d 执行结束n", goroutine.ID)
}()
goroutine.Fn()
}(g)
}
}
完整使用示例
编写主函数测试调度器的功能,创建多个协程并启动调度:
func main() {
scheduler := NewScheduler()
// 启动调度循环
go scheduler.schedule()
// 创建3个测试协程
scheduler.Go(func() {
time.Sleep(100 * time.Millisecond)
fmt.Println("第一个协程执行完成")
})
scheduler.Go(func() {
time.Sleep(50 * time.Millisecond)
fmt.Println("第二个协程执行完成")
})
scheduler.Go(func() {
time.Sleep(150 * time.Millisecond)
fmt.Println("第三个协程执行完成")
})
// 等待所有协程执行完成
time.Sleep(500 * time.Millisecond)
}
实现注意事项
上述示例是一个极简的调度器实现,仅用于理解调度的基本逻辑,和原生调度器相比还有很多不足:
- 没有实现本地队列和全局队列的区分,所有协程都放在同一个队列中,高并发下锁竞争会比较明显
- 没有处理协程阻塞的场景,原生调度器中当G阻塞时,M会解绑P,P会绑定其他M继续执行其他G
- 没有实现工作窃取机制,原生调度器中当P的本地队列为空时,会从其他P的队列或者全局队列中窃取协程执行
如果需要在生产环境中优化协程调度,建议优先基于Golang原生的调度机制进行参数调优,比如合理设置GOMAXPROCS的值,避免不必要的协程阻塞,减少协程的频繁创建和销毁。
Golang协程调度器goroutinego_schedulerruntime修改时间:2026-06-10 02:00:17