Go 中 goroutine 生命周期由 runtime 自动管理,创建后立即入 P 本地队列(≤256 个)或全局队列,状态为 _Grunnable,调度器自动调度;阻塞时自动让出 P,不阻塞 M 或其他 G。
go 中的 g(goroutine)生命周期完全由 runtime 自动管理,开发者不需、也不应手动干预其创建后的存活与销毁逻辑。
goroutine 创建后就进入调度队列,不依赖显式启动
调用 go f() 后,G 立即被放入 P 的本地运行队列(或全局队列),等待 M 获取执行。它不会“卡住不动”,也不需要你调用类似 Start() 或 Resume() 的方法来激活。
- 如果 P 本地队列未满(≤256 个),新
G优先进入该队列 - 若本地队列已满,runtime 会将其中一半迁移至全局队列,再插入新
G - 此时
G状态为_Grunnable,调度器会在下一个调度周期内安排它运行
goroutine 阻塞时自动让出 P,不阻塞 M 或其他 G
当 G 执行阻塞操作(如 time.Sleep()、ch 、net.Conn.Read()、sync.Mutex.Lock()),它不会把整个 M 拖入休眠;runtime 会将其状态置为 _Gwaiting 或 _Gsyscall,并立即从当前 M 上剥离。
- 若因系统调用阻塞(
_Gsyscall),M 可能被解绑,P 被 hand off 给其他空闲 M - 若因通道/锁/定时器阻塞(
_Gwaiting),G被挂起在对应数据结构上(如hchan、mutex.sema),唤醒由 runtime 异步触发 - 此时 M 可立刻从同 P 的本地队列或全局队列中拉取下一个
G运行
goroutine 退出后栈和元信息由 runtime 异步回收
G 函数返回即结束生命周期,状态变为 _Gdead,但它的内存不会立刻释放——runtime 将其归还至 G 复用池(allgs + gFree 列表),供后续 go 调用复用。
- 栈内存可能被 shrink(缩容)甚至归还 OS,取决于当前栈大小与使用频率
- 频繁创建/退出小 goroutine(如每请求一个
go handler())几乎无 GC 压力,因为G对象本身极轻(约 2KB 初始栈 + 少量结构体) - 不要用
runtime.GC()强制回收刚退出的G——这毫无意义,且干扰 GC 正常节奏
长生命周期 G 不需要 runtime.Gosched(),抢占已由信号机制保障
即使一个 G 连续执行纯计算循环(如密集矩阵运算),只要它运行时间超过 10ms(Go 1.14+ 默认抢占阈值),runtime 就会通过 SIGURG 信号中断其所在 M,并强制调度其他 G。你写 for { ...; runtime.Gosched() } 不仅多余,还会引入不必要的调度开销。
- 仅在 Go 1.14 之前、且确定运行于单核环境、且存在不可拆分的超长计算循环时,才需谨慎考虑
runtime.Gosched() - 现代 Go 中更推荐:用
context.Context控制取消、用time.AfterFunc分片任务、用runtime.LockOSThread()仅在必须绑定 OS 线程时使用 - 真正容易被忽略的是:
G的栈增长边界检查、defer 链表累积、以及闭包捕获大对象导致的意外内存驻留——这些比调度干预更影响长周期行为











