在高并发的Go服务里,临时存储往往表现为请求级缓冲区,比如序列化中间数组或批量读取用的字节切片。如果每次处理都新建再丢弃,会给GC带来明显负担。通过复用手段可以有效缓解这一问题。

为什么需要缓冲区复用
Go的垃圾回收机制虽然易用,但高频的短生命周期对象会产生大量小对象分配。使用sync.Pool可以把暂时不用的缓冲区交给池子,下次直接取出,避免重复申请内存。
sync.Pool基本用法
下面是一个复用字节缓冲区的简单例子:
package main
import (
"bytes"
"sync"
)
// 定义一个全局的缓冲区池
var bufPool = sync.Pool{
New: func() interface{} {
// 新建时返回大小为1024的字节缓冲
return bytes.NewBuffer(make([]byte, 0, 1024))
},
}
func process(data string) string {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset() // 使用前先清空内容
buf.WriteString(data)
result := buf.String()
bufPool.Put(buf) // 用完放回池中
return result
}
func main() {
_ = process("hello")
}
切片预分配减少扩容
除了池化,提前给切片预留容量也能降低内存搬移。用make时指定长度和容量,可避免向操作系统频繁要内存。
对比示例
| 方式 | 分配次数 | 适用场景 |
|---|---|---|
| 无预分配append | 多次 | 数量未知小数据 |
| make预分配 | 一次 | 已知上限批量处理 |
预分配代码
package main
func buildSlice(n int) []int {
// 已知需要n个元素,直接预留容量
s := make([]int, 0, n)
for i := 0; i < n; i++ {
s = append(s, i)
}
return s
}
bytes.Buffer的注意事项
使用bytes.Buffer做临时存储时,记得调用Reset而不是新建。另外,若把Buffer的底层数组暴露出去,要保证使用期不与池化冲突。
小结
通过sync.Pool复用、切片预分配以及规范Buffer使用,可以在Go语言中高效管理临时存储,显著减少内存分配和GC开销,提升程序整体性能。