Go Runtime(十):Timer实现

本文源码基线为 Go 1.26.2、Linux/amd64。Go 1.23 重写了 Timer Channel 的可回收性与 Stop/Reset 保证;新语义是否默认生效还取决于主模块的 go 版本和 godebug/GODEBUG 设置,不能只看工具链版本。 Timer 同时服务 time.Sleep、time.Timer、time.Ticker、AfterFunc 和网络 Deadline。到期、Stop、Reset 和 Channel 的语义还会受到模块版本与调试开关影响。 先锁定可观察语义 time.NewTimer 的源码仍写 make(chan Time, 1)。新模式(asynctimerchan=0)下 runtime 借助 hchan.timer 对外呈现同步、容量为零的语义;asynctimerchan=1 恢复 Go 1.23 以前的异步 Timer Channel 行为。因此不能只看 make 参数或 go version 推断 cap(t.C) 和 Stop/Reset 语义。 三种有效模式对程序呈现的差异是: asynctimerchan Channel语义 Stop/Reset返回后的旧值 未到期且不可达的Timer 0 同步,cap(C) == 0 保证不会收到 可以被GC回收 1 Go 1.23以前的异步路径,cap(C) == 1 可能收到 不能在到期或Stop前被GC回收 2 runtime新实现,但呈现异步语义,cap(C) == 1 可能收到 可以被GC回收 这里的“从 Go 1.23 起”是模块语义版本,不只是所用 toolchain 的版本。asynctimerchan 在 internal/godebugs 表中标记为 Go 1.23 变更项,旧于 go 1.23 的主模块默认取旧值 1;go 1.23 及更新主模块默认取新值 0。go.mod/go.work 的 godebug 指令、源码中的 //go:debug 或进程环境 GODEBUG 都可以显式覆盖它。调试 Timer 时必须记录最终构建配置。 ...

August 18, 2026

Go Runtime(二):GMP与Goroutine调度

本文源码基线为 Go 1.26.2、Linux/amd64。调度器变化频繁;函数名、队列容量和分支顺序均以固定版本源码为准。 Go 调度器解决的核心问题是:怎样把大量 goroutine 映射到较少的 OS 线程,同时控制竞争、阻塞和调度延迟。 理解调度器不需要记住所有函数。先抓住三件事:执行资格属于 P,等待发生在 G 上,真正运行机器指令的是 M。 先观察:两个P为什么会有更多线程 下面的程序把 GOMAXPROCS 设为 2,同时制造 runnable G、Timer 和一个阻塞 syscall: GOpackage main import ( "runtime" "syscall" "time" ) func main() { runtime.GOMAXPROCS(2) for range 20 { go func() { for { runtime.Gosched() } }() } var pipe [2]int if err := syscall.Pipe(pipe[:]); err != nil { panic(err) } defer syscall.Close(pipe[0]) defer syscall.Close(pipe[1]) go func() { var b [1]byte _, _ = syscall.Read(pipe[0], b[:]) }() time.AfterFunc(500*time.Millisecond, func() { _, _ = syscall.Write(pipe[1], []byte{1}) }) time.Sleep(3 * time.Second) } 先构建再观察调度快照,避免把 go run 自身的编译进程混入输出: ...

August 18, 2026