<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>抢占 on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/%E6%8A%A2%E5%8D%A0/</link><description>Recent content in 抢占 on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 02 Jun 2026 17:36:48 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/%E6%8A%A2%E5%8D%A0/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 为什么不怕 goroutine 死循环：抢占不是玄学，是一套停机协议</title><link>https://blog.cpdd.fyi/posts/go-gmp-preemption-gc/</link><pubDate>Tue, 02 Jun 2026 17:36:48 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-gmp-preemption-gc/</guid><description>&lt;p&gt;把 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 设成 1，再启动一个没有函数调用、没有 I/O、没有 sleep 的死循环。&lt;/p&gt;
&lt;p&gt;按很多人对 goroutine 的理解，程序应该完了：唯一的 P 被这个 goroutine 一直占着，其他 goroutine 没机会运行，GC 想停世界也停不下来。&lt;/p&gt;
&lt;p&gt;但 Go 1.14 之后，这个直觉已经不完整了。&lt;/p&gt;
&lt;p&gt;你可以跑一个很小的实验：一个 goroutine 进入 tight loop，另一个 goroutine 每 200ms 打印一次。默认运行时，ticker 还能继续打印；如果关掉异步抢占，程序可能在预期时间内完全没有输出。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; assets/go-gmp-series/03-preemption-gc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; go run examples/preemption-demo.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;GODEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;asyncpreemptoff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; go run examples/preemption-demo.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;本机 Go 1.25.4 验证结果是：默认情况下 ticker 正常输出并退出；关闭 async preempt 后，2 秒内没有任何输出，命令超时。&lt;/p&gt;
&lt;p&gt;这件事不是“goroutine 很轻”能解释的。&lt;/p&gt;
&lt;p&gt;它真正说明的是：现代 Go 调度器不再完全依赖 goroutine 自觉让出 CPU。runtime 有能力发现某个 G 占 P 太久，然后用同步抢占或异步抢占，把它带回调度体系里。&lt;/p&gt;
&lt;p&gt;但这里最容易讲歪。&lt;/p&gt;
&lt;p&gt;Go 不是每 10ms 硬切一次 goroutine，也不是收到一个信号就能在任意机器指令上停住。抢占从来不是“想停就停”。它更像一份停机协议：runtime 要能停下这个 G，还要保证 GC 看得懂它的栈、寄存器和对象引用。&lt;/p&gt;</description></item></channel></rss>