知其然,知其所以然
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 取消 真实验证 动画演示
Go 内部原理 2026-10-10

worker pool:固定并发数的几种写法对比

在 Go 1.27.1 里比较固定并发数的五种写法:2000 个 5 ms 任务、上限 50 时,固定 worker、先占位的信号量、errgroup 峰值只有 52 个 goroutine,先起 goroutine 再占位的写法峰值 2002;100 万个极短任务、上限 8 时,固定 8 个 worker 414 ms、累计分配 27 KiB,每任务一个 goroutine 的限并发写法 521–649 ms、分配 31–39 MB。

Go worker pool 并发控制 errgroup 信号量 真实验证 动画演示
Go 内部原理 2026-10-10

select 与超时:time.After 在循环里的坑

在 Go 1.27.1 里实测 select:两个 channel 都就绪时 100 万次选择 50.0%/50.0%;普通 select 会让低优先级被选中约一半,嵌套 select 为 0;结果和超时同时就绪时 10 万次里结果 50017 次、超时 49983 次;已关闭的 channel 不置 nil 会空转,置为 nil 后只循环 12 次;select+default 忙等待占满 CPU。

Go select 超时 优先级 nil channel 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号