← 中文资源

简体中文版本

Sync Pool 字节缓冲复用

用 sync.Pool 复用字节缓冲削减热路径分配:Get 取用 Put 归还,归还前必须重置,兜底构造器保证池空可用。

阅读英文原文

示例说明

高 QPS 路径上每次请求都新建缓冲区会把 GC 压力推高——分配、扫描、回收全程开销。sync.Pool 的对象池让缓冲区在请求间复用:Get 取出任意空闲对象,用完 Put 归还,池空时 New 构造新对象兜底。归还前 Reset 是正确性红线——不清零会把上一个使用者的数据泄漏给下一个。

技术要点

  • Get 与 Put 严格配对成生命周期
  • 归还前 Reset 防跨请求数据串味
  • New 兜底构造保证池空不失败

代码解读

renderJSON 从池里取缓冲,defer 里先 Reset 再归还——defer 的执行顺序保证异常路径也会归还;写入用 WriteString 与 Fprintf 组合避免拼接临时串。main 的并发循环演示多 goroutine 共享池的安全性:池本身并发安全,业务侧只需归还纪律。

使用建议

池对象不宜过大,巨型缓冲会撑住堆不释放。GC 清空池是正常行为,别指望池永久保量。存接口值有装箱开销,指针类型最合适。

代码示例

package main

import (
	"bytes"
	"fmt"
	"sync"
)

var bufferPool = sync.Pool{
	New: func() interface{} {
		return new(bytes.Buffer) // 池空时的兜底构造
	},
}

func renderJSON(name string, score int) string {
	buf := bufferPool.Get().(*bytes.Buffer)
	defer func() {
		buf.Reset()         // 必须清零,防止跨使用者数据泄漏
		bufferPool.Put(buf) // 归还前重置容量状态
	}()

	buf.WriteString("{\"name\":\"")
	buf.WriteString(name)
	buf.WriteString("\",\"score\":")
	fmt.Fprintf(buf, "%d}", score)
	return buf.String()
}

func main() {
	results := make([]string, 0, 8)
	var wg sync.WaitGroup

	for i := 0; i < 8; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			results = append(results[:n:n], renderJSON(fmt.Sprintf("user-%d", n), n*10))
		}(i)
	}
	wg.Wait()

	for _, r := range results {
		fmt.Println(r)
	}
}