gnet没有双端队列内存池,其核心是ringbuffer+bytebufferpool+ants三层协同:ringbuffer仅支持单向读写,不提供pushfront/popback;bytebufferpool负责高频[]byte分配,需严格get/put;ants仅调度任务,不管理内存。
gnet 没有内置“双端队列内存池”,它用的是 ringbuffer + bytebufferpool + ants 三层协同机制,误以为有 deque 是因为混淆了 ringbuffer 的读写端独立推进特性。
RingBuffer 不是 deque,别在代码里调用 PushFront/PopBack
RingBuffer 在 gnet 中仅支持单向顺序读写:数据只能 Write 到写端、Read 从读端消费,指针线性前移并自动绕回。它不提供 PushFront、PopBack、Insert 等任意位置操作——这些是 deque 的语义,RingBuffer 根本没有对应方法。
常见错误现象:
- 编译报错
undefined: rb.PushFront或运行时 panic “method not found” - 试图用 RingBuffer 做消息重排序、头部插帧、尾部丢弃等逻辑,结果发现无法实现
正确做法:
- 若需头部插入(如协议前缀补全),应先用
bytebufferpool.Get()拿一个临时ByteBuffer,拼接后再写入 RingBuffer - 若需跳过已读但未清理的数据,只需移动读指针(
rb.AdvanceRead),空间自然复用,无需“删除” - 所有对 RingBuffer 的操作必须严格遵循生产者-消费者模型,避免跨 goroutine 直接读写同一实例
bytebufferpool 是你最该关注的内存池,不是 RingBuffer 本身
真正承担高频字节切片分配压力的是 bytebufferpool,它管理的是底层 []byte,而非 *RingBuffer 结构体。gnet 在协议解析、响应序列化、分包粘包处理等场景大量依赖它。
使用要点:
- 每次调用
bytebufferpool.Get()后,务必在作用域末尾defer bytebufferpool.Put(buf),否则内存泄漏风险极高 - 获取的
ByteBuffer默认容量不固定,首次写入可能触发扩容;如需稳定性能,可用buf.B = buf.B[:0]清空长度,而非直接buf.Reset()(部分版本无此方法) - 不要把大 buffer(> 32KB)塞进 pool:Go 的 mcache 对大对象绕过
sync.Pool,Put 进去基本不会被复用 - 避免在 long-running goroutine 中长期持有
ByteBuffer:Pool 按 P 局部缓存,长时间不 Put 会导致本地 P 缓存失衡
RingBuffer 池和 ants 池职责分明,别混用或漏掉 Put
gnet 显式提供了三个可复用资源池,各自边界清晰:
-
pkg/pool/ringbuffer.Get()/Put():复用*RingBuffer实例(结构体本身,不含底层字节内存) -
bytebufferpool.Get()/Put():复用底层[]byte内存块 -
ants.Submit():提交任务到 goroutine 池,避免每连接启 goroutine
容易踩的坑:
- 只调用了
ringbuffer.Get()却忘了Put(),导致 RingBuffer 实例持续增长,最终 OOM - 误以为
ants能管理内存——它只管执行单元,和[]byte或RingBuffer完全无关 - 在自定义
EventHandler.OnOpened中 new 一个 RingBuffer 而不用池,等于绕过所有优化
最关键的细节往往藏在复用链路的衔接处:RingBuffer 的 Write 方法内部会按需从 bytebufferpool 申请新内存,但不会自动 Put 回去;而你手动 Get 的 ByteBuffer 必须自己 Put。这两层生命周期完全独立,漏掉任意一环,优化就归零。











