功能说明
高并发服务中,每次请求都创建新的 bytes.Buffer 会把压力转给垃圾回收器。sync.Pool 提供临时对象缓存:取用时若池中有空闲对象就复用,没有则调用 New 创建;用完后归还,供后续请求使用。池中的对象可能在任何一次垃圾回收时被清理,因此不能把池当作持久缓存,也不能假设归还后一定还能取到同一个对象。
本例封装 JSON 编码函数,从池中借缓冲区写入编码结果,再把内容复制到新的字节切片返回。复制是必要的,因为归还后缓冲区会被下一次调用重置,直接返回其内部切片会导致数据竞争和内容被覆盖。
关键代码
bufferPool 的 New 返回 *bytes.Buffer,Get 后用类型断言取回。每次使用前调用 Reset 清空旧内容,防止上一次请求的数据泄漏到本次结果。defer 把归还安排在函数退出时,即使编码失败也会执行。json.NewEncoder 会在末尾追加换行,这一点在比较结果时要考虑。复制使用 make 加 copy,长度以 buffer.Len 为准,返回的切片与池对象完全分离。
使用注意
sync.Pool 不适合存放带敏感数据的对象,除非在归还前彻底清零;Reset 只改长度,不擦除底层数组内容,内存中仍可能残留旧数据。池化对象应大小适中,缓存超大缓冲区会长期占用内存。不要在归还后继续使用缓冲区或其切片。对低并发或编码量很小的场景,池化带来的复杂度可能超过收益。Go 的池在垃圾回收时可能清空,性能测试应在真实负载下进行。
验证方式
运行脚本,先输出一行 JSON,再输出 true,说明复制后的字节与预期完全一致,包括编码器追加的换行。并发调用该函数并开启竞态检测,不应报告数据竞争。可把 defer 归还删除,观察池失效后分配次数上升,但结果仍然正确。
代码示例
package main
import (
"bytes"
"encoding/json"
"fmt"
"sync"
)
var bufferPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
func EncodeJSON(value any) ([]byte, error) {
buffer := bufferPool.Get().(*bytes.Buffer)
buffer.Reset()
defer bufferPool.Put(buffer)
if err := json.NewEncoder(buffer).Encode(value); err != nil {
return nil, err
}
out := make([]byte, buffer.Len())
copy(out, buffer.Bytes())
return out, nil
}
func main() {
type payload struct {
ID int `json:"id"`
Name string `json:"name"`
}
encoded, err := EncodeJSON(payload{ID: 1, Name: "Ada"})
if err != nil {
panic(err)
}
fmt.Print(string(encoded))
fmt.Println(bytes.Equal(encoded, []byte("{\"id\":1,\"name\":\"Ada\"}\n")))
}