for {} 会占满 cpu 是因为它不包含任何让出 cpu 的操作,导致 goroutine 持续抢占时间片;加 time.sleep(1ms) 可有效降低占用至 0.5% 以下,而 time.sleep(0) 无效。

为什么 for {} 会让 CPU 占满?
Go 中写 for {} 确实是无限循环,但它不包含任何让出 CPU 的操作,编译器和运行时也不会自动插入休眠或调度点。goroutine 会持续抢占时间片,尤其在单核或 GOMAXPROCS=1 时更明显。这不是 Go 的 bug,而是“空转”本就会耗尽可用算力。
用 time.Sleep 是最直接的解法
加一个足够短但非零的休眠,就能把 CPU 占用压到几乎为 0。关键不是“睡多久”,而是“必须让 runtime 有机会调度其他 goroutine 或交还 OS 时间片”。
-
time.Sleep(1 * time.Millisecond):对大多数轮询场景够用,CPU 占用通常低于 0.5% - 别用
time.Sleep(0):它不会触发调度,效果等同于没睡 - 避免动态计算休眠时长(比如根据上一轮耗时调整),除非真有实时性要求;多数后台监听/健康检查场景固定值更稳
- 如果循环体本身有阻塞操作(如
ch := ),就不需要额外 <code>Sleep——那是另一种“天然让出”方式
用 runtime.Gosched() 要小心场景
runtime.Gosched() 会主动让出当前 goroutine 的执行权,但它不保证休眠、也不释放 OS 线程,只提示调度器“我可以换人了”。在无其他可运行 goroutine 时,它可能立刻被重新调度,仍导致高 CPU。
- 仅适合配合大量 goroutine 并发且需精细控制协作的场景
- 单独用于
for {}中基本无效,不如time.Sleep - Go 1.21+ 中它的行为更保守,但语义没变:它不等价于“暂停”,只是“建议调度”
真正该考虑的是:你真的需要无限循环吗?
很多所谓“无限循环”其实是想等某个信号(文件变化、网络事件、定时触发)。硬写循环 + Sleep 不仅难维护,还掩盖了真实意图。
- 文件监听优先用
fsnotify库,而不是每 100msos.Stat - 定时任务用
time.Ticker,而非for { do(); time.Sleep() } - 等待关闭信号,直接
阻塞,比轮询 <code>done != nil干净得多 - 若必须轮询(比如某些硬件接口),至少把
Sleep时长暴露为配置项,方便压测调优
空循环本身没有错,但把它当成默认模式,往往说明还没找到更贴合问题本质的并发原语。











