知其然,知其所以然
Go 内部原理 2026-10-10

竞态检测器:ThreadSanitizer 在 Go 里的开销与漏报

在 Go 1.27.1(arm64 macOS)里实测 -race:counter++ 的竞争被报告(退出码 66)而普通版无提示;睡眠“排序”的读写和原子写+普通读也算竞争;没执行到的竞争分支不报;GORACE 的 exitcode 与 halt_on_error;10 万个 goroutine 同时存活无上限报错;访存密集的循环慢 31.2 倍、内存 3.6 倍,争锁程序慢 3.5 倍、内存 5.8 倍。

Go 竞态检测器 race ThreadSanitizer data race 真实验证 动画演示
Go 内部原理 2026-10-10

Go 内存模型:happens-before 在 channel、锁和 atomic 之间怎么建立

在 Go 1.27.1(arm64)里验证内存模型:用 -race 逐个检验 12 种同步,channel、close、mutex、atomic、Once、WaitGroup、go 语句都建立了 happens-before,Sleep、Gosched 和容量 1 channel 只 send 一次不行;SB 测试里普通变量 200 万轮出现 27591 次 r1=r2=0,sync/atomic 为 0 次;并摘录文档中各条同步规则。

Go 内存模型 happens-before 原子操作 真实验证 动画演示
Go 内部原理 2026-10-10

sync/atomic:原子操作与 Mutex 的性能边界

在 Go 1.27.1(arm64 macOS,8 个线程)里量原子操作与 Mutex 的边界:无竞争时 atomic.Add 4.1 ns、Mutex 7.7 ns;8 个 goroutine 争同一个计数器时 atomic 53.7 ns、Mutex 157.2 ns、CAS 循环 254.9 ns;互不共享的原子计数器相邻放置比隔开 128 字节慢约 70 倍(57.1 ns 对 0.8 ns);并看 arm64 上编译出的 LDADDALD 与 CASALD。

Go sync/atomic 原子操作 Mutex 伪共享 真实验证 动画演示
Go 内部原理 2026-10-10

atomic.Value 与 atomic.Pointer:无锁读配置的写法

在 Go 1.27.1(arm64 macOS)里验证无锁读配置的写法:8 个 goroutine 读时 atomic.Pointer.Load 0.1 ns、RWMutex.RLock 78.8 ns;原地覆盖共享结构体会让读者读到 A≠B 106250 次(-race 报竞争),而“新建再 Store 指针”为 0 次;atomic.Value 的三种 panic 原文;值装箱会多一次分配;go vet 能发现复制含 atomic 值的结构体。

Go atomic.Value atomic.Pointer 无锁读 配置热更新 真实验证 动画演示
Go 内部原理 2026-10-10

sync.Pool:对象什么时候被回收,victim cache 是什么

在 Go 1.27.1 里验证 sync.Pool:放入 100 个对象后,一次 GC 还能取回 99 个,两次 GC 全部丢失(对应 poolCleanup 里“主缓存→victim→丢弃”);Put 切片本身比 Put 指针多一次分配;复用 bytes.Buffer 从 714 ns/6 次分配降到 192 ns/0 次分配,4 核并行 987 ns 降到 71 ns;200 万次借用 4 KiB 缓冲区,GC 次数从 2611 降到 0。

Go sync.Pool victim cache 对象复用 GC 真实验证 动画演示
Go 内部原理 2026-10-10

sync.Map:什么场景比 map 加锁快

在 Go 1.27.1(arm64 macOS)里量 sync.Map:8 个 goroutine 读多写少时 5.2–6.1 ns,RWMutex+map 要 98–132 ns;50% 写时 32 ns 对 86–95 ns;但单 goroutine 下反而更慢(29.6 对 16.8 ns),100 万个键的内存是普通 map 的 3.2 倍;并确认它在 1.27 里建立在内部的 HashTrieMap 上。

Go sync.Map 并发map HashTrieMap 真实验证 动画演示
Go 内部原理 2026-10-10

RWMutex:写者饥饿和读者递归加锁的行为

在 Go 1.27.1 里实测 RWMutex:持读锁时递归 RLock 遇上排队的写者会死锁(fatal error);写者排队后新读者要等(R2 在 20 ms 请求,122 ms 才拿到);8 个连续读的 goroutine 下写者每次只等 0.60–1.01 ms;8 个只读 goroutine 下,临界区 100 µs 时 RWMutex 比 Mutex 快约 8.1 倍,而几乎不持有时只快 1.4 倍。

Go RWMutex 读写锁 死锁 写者饥饿 真实验证 动画演示
Go 内部原理 2026-10-10

sync.Cond:Wait 为什么必须放在循环里

在 Go 1.27.1 里验证 sync.Cond.Wait 为什么要放在循环里:8 个消费者、Broadcast 时,Wait 放 if 里 3000 个元素中有 2998 次醒来后队列是空却照常取,放 for 里是 0 次;Signal 唤醒 1 个、Broadcast 唤醒 8 个;Signal 先于 Wait 会丢(300 ms 后仍卡住);Cond 来回传递 308 ns,比无缓冲 channel 的 261 ns 慢。

Go sync.Cond 条件变量 Wait Broadcast 真实验证 动画演示
Go 内部原理 2026-10-10

errgroup:第一个错误取消其他任务的实现

用 golang.org/x/sync v0.24.0 实测 errgroup:一个任务出错 51 ms 后其余 4 个任务都因 ctx 取消结束(WaitGroup 要等 1001 ms);多个错误只返回第一个;SetLimit(3) 时最大并发恰好是 3、g.Go 会阻塞而 TryGo 返回 false;任务里 panic 会让整个进程崩溃(Wait 处 recover 不到);WithContext 的 ctx 在 Wait 返回后一定被取消;并读源码里的信号量与 errOnce。

Go errgroup 并发 取消 SetLimit 真实验证 动画演示
Go 内部原理 2026-10-10

singleflight:合并重复请求,和缓存击穿

用 golang.org/x/sync v0.24.0 的 singleflight 实测:100 个并发请求同一个键,加载函数从 100 次降到 1 次;缓存刚过期的 200 个请求,后端从 200 次降到 1 次、总用时 150 ms 降到 101 ms;但它不是缓存(顺序调用 3 次就执行 3 次);错误被所有等待者共享;第一个调用方的 ctx 超时会连累第二个调用方;加载函数 panic 会让所有调用方 panic。

Go singleflight 缓存击穿 并发 合并请求 真实验证 动画演示
Go 内部原理 2026-10-10

令牌桶限流:x/time/rate 的 Allow、Wait 与 Reserve

用 golang.org/x/time v0.16.0 的 rate 实测令牌桶:桶满时 Allow 连放 5 个再拒绝,空闲 1.2 秒也只能一口气放 5 个;Wait 以 20/s 放行,间隔 49.9–51.1 ms;n 超过 burst 或等待会超过 ctx 期限时立即返回错误;Reserve 的预约依次排到 100/200/300 ms;1、8、64 个 goroutine 共用一个限流器,吞吐都是约 1003 次/秒。

Go 限流 令牌桶 x/time/rate Allow Wait Reserve 真实验证 动画演示
Go 内部原理 2026-10-10

channel 流水线:fan-out、fan-in 与取消

在 Go 1.27.1 里实测 channel 流水线:消费者提前走掉而没有取消机制时泄漏 100 个 goroutine,加 cancel 后为 0;4 个 worker fan-out 后输出有 794 处逆序;第二级 worker 从 1 个增加到 8 个,2 万个元素从 596 ms 降到 114 ms(约 5.2 倍);级间 channel 容量从 0 加到 256,每个元素的耗时从 594 ns 降到 129 ns。

Go channel 流水线 fan-out fan-in 取消 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号