goroutine是go运行时用m:n模型在用户空间调度的轻量执行单元,由g、m、p协作实现:g代表协程,m为os线程,p为逻辑处理器;阻塞时m释放p,其他m可接管,故少量线程可支撑数万goroutine。

goroutine 不是操作系统线程,也不是用户态线程库(如 libco)那种显式 yield 的协程,它是 Go 运行时(runtime)用 M:N 模型在用户空间调度的轻量执行单元。它的实现不依赖你写 yield 或 await,而是由 runtime 在安全点自动挂起和恢复。
goroutine 调度靠 G、M、P 三者协作
Go 调度器不是黑盒,它由三个核心结构体驱动:
-
G:代表一个 goroutine,保存栈、寄存器上下文、状态(如_Grunnable、_Grunning、_Gsyscall) -
M:对应一个 OS 线程(pthread),负责实际执行代码;它必须绑定一个P才能运行 Go 代码 -
P:逻辑处理器(process),不是 CPU 核心,而是调度资源池;默认数量 =runtime.NumCPU(),可通过GOMAXPROCS修改
当 G 阻塞在 channel 操作、网络 I/O 或 time.Sleep 时,M 会释放 P 并进入系统调用阻塞;其他空闲 M 可立即获取该 P 继续运行队列里的 G —— 这就是为什么少量线程能支撑数万 goroutine。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
调度时机是隐式的,但有明确触发点
Go 不需要你手动让出控制权,但 runtime 会在以下确定位置插入调度检查(即「安全点」):
- channel 发送/接收(阻塞时)
- 网络 I/O(底层由
netpoll封装 epoll/kqueue,不阻塞 M) - 系统调用返回前(
_Gsyscall→_Grunnable) - 函数调用栈扩容(stack growth)
- 垃圾回收辅助标记阶段(GC work)
- Go 1.14+:长时间运行的循环中会插入股检测(preemption signal)
注意:for {} 这种纯计算循环在旧版本(
创建和销毁开销极低,但栈管理有细节
每个 goroutine 初始栈仅 2KB(Go 1.19+),按需动态增长/收缩,由 runtime 在栈边界检查时完成;这避免了固定大栈的内存浪费,也规避了传统线程栈溢出风险。
- 新建:
go f()触发newproc,分配G结构体 + 小栈,入全局或本地运行队列 - 销毁:函数返回后,
G置为_Gdead,其栈可能被复用或归还给内存池,不立即释放 - 栈迁移代价存在:每次栈分裂都要拷贝数据,高频小对象分配+递归过深可能触发多次迁移
goroutine 的“轻量”只在调度和内存层面成立;一旦它做同步阻塞操作(比如直接调用 syscall.Read 且未用 net 包封装),就会导致整个 M 卡住,拖慢所有绑定到该 P 的 goroutine。所以务必使用 Go 标准库封装过的 I/O,别裸写系统调用。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










