# 記憶體與併發原語

> 零分配靠三件事：逃逸分析決定變數落在 stack 還是 heap，sync.Pool 重用短命物件攤平 GC，同步原語單值用 atomic、一塊狀態用 Mutex、讀遠多於寫才用 RWMutex。

原文：https://sheng.page/posts/go-memory-and-sync-primitives/ · 發布：2025-09-28 · 作者：Sheng

零分配靠的是逃逸分析加物件重用加選對同步原語。這篇把 `sync.Pool`、`atomic`、Mutex/RWMutex 擺一起講，之後看 gortex 的零分配 hot path 才不會卡。

## 逃逸分析：stack 還是 heap

變數放 stack 還是 heap，由編譯器在編譯期決定，不是看你寫 `new` 還是 `&`。能證明變數不會活過函式作用域就放 stack（不歸 GC 管，函式返回就回收）；一旦被外部引用，像回傳指標、塞進 interface、被 closure 捕捉、存進會逃逸的結構，就 escape 到 heap。

```go
func New() *T { return &T{} } // &T 逃逸到 heap：指標被回傳出去了
```

用 `go build -gcflags='-m'` 可以看每個變數的逃逸決策。零分配的第一步，就是砍掉不必要的 escape。

## sync.Pool：重用降 GC 壓力

`sync.Pool` 重用短命物件，把配置成本與 GC 壓力分散掉。`Get` 拿一個（池子空了就呼叫 `New`），用完 `Put` 還回去：

```go
var bufPool = sync.Pool{New: func() any { return new(bytes.Buffer) }}

b := bufPool.Get().(*bytes.Buffer)
b.Reset()            // 放回前/拿出後要清空
defer bufPool.Put(b)
```

兩個重點：還回去前要 reset，避免髒資料外洩；而且不能假設池子一定留著你的物件，GC 會清掉 Pool。gortex 每條請求的 Context 就是從這種 pool 拿、用完歸還（〈[零分配的秘密：Context pool 與 smartParams](/posts/gortex-zero-allocation-context/)〉）。

## atomic vs Mutex vs RWMutex

- `atomic`：對單一整數或指標做無鎖操作（`AddInt64`、`CompareAndSwap`），最快，但只保護得了那一個值。
- `sync.Mutex`：保護一段臨界區，適合一次要改好幾個欄位。
- `sync.RWMutex`：讀多寫少時，多個讀並行、寫獨佔；但它比 Mutex 重，讀寫比不夠懸殊反而更慢。

選法一句話：單一計數用 `atomic`，一塊狀態用 `Mutex`，讀遠多於寫才考慮 `RWMutex`。gortex 的 sharded metrics 同時用上 `atomic` 計數與 per-shard 鎖（〈[併發計數與韌性：sharded metrics 與 circuit breaker](/posts/gortex-metrics-and-circuit-breaker/)〉）。

## Effective Go 精選：Data

Effective Go 的 [Data](https://go.dev/doc/effective_go#data) 講配置的基本盤：`new(T)` 配一塊歸零記憶體、回 `*T`；`make` 專給 slice / map / channel 並做初始化；composite literal `&T{...}` 直接給值。搞懂這幾個配置路徑，才分得出哪些值落在 heap、哪些能留在 stack 省掉。


## 補充筆記

- 逃逸分析決定 stack/heap，`-gcflags='-m'` 看；回傳指標、進 interface 容易逃逸。
- `sync.Pool` 重用物件攤平 GC；放回前 reset，別假設物件一定還在。
- 選同步原語：`atomic`（單值）→ `Mutex`（一段狀態）→ `RWMutex`（讀多寫少才划算）。
- 這三樣是 gortex 零分配 hot path 的前置：零分配那篇的 Context pool 與 metrics 那篇的 sharded 計數器都建在這上面。

延伸：本系列接著用這些觀念拆 gortex 框架，原始碼見 [yshengliao/gortex](https://github.com/yshengliao/gortex)。

---

> 想法與技術判斷出自 Sheng，和 Claude 一起起草 · 環境 Go 1.25（當時最新 release）· 本系列為事後回填整理 · 列入 20260613 blog 翻新計劃，新漆未乾。