在科学上网服务中,客户端不断地向shadowsocks服务器发送数据,服务器从网络中读取经过加密的数据然后进行解密操作,为了避免重复的分配内存和释放内存操作,我们引入 LeakyBuffer 这种编程模型,LeakyBuffer 通常也被叫做 LeakyBucket ,或者说是漏桶模型。LeakyBuffer 维护了固定个数个字节数组(free list),如果我们的程序需要使用内存, 那么它会从 LeakyBuffer 维护的 freelist 去拿,如果 freelist 中没有了,则再重新make一个,当程序使用完了这段内存,不像通常那么被golang的runtime进行垃圾回收操作,我们把它重新放回 LeakyBuffer 维护的 freelist 中。
下面我们来看一下 Leaky Buffer 的实现吧。
1 | const leakyBufferSize = 4096 |
LeakyBuffer 的实现非常简单,它主要维护了两个变量,一就是每个 buffer 的大小,而就是一个 freelist ,真正用于维护我们的内存。
在上面的代码中我们定义每个 buffer 的大小是4096,这里的单位是字节,freelist 的长度是2048,所以我们这个 LeakyBuffer 总共可以使用的内存大小就是 4096 * 2048 B 也就是 8MB ,这对于一般的网络应用已经足够了。
1 | var leakyBuf = NewLeakyBuffer(maxNum, leakyBufferSize) |
NewLeakyBuffer 返回一个 LeakyBuffer 指针,它维护的 freelist 就是一个容量为n的通道。
下面我们来看看如何使用 LeakyBuffer ,LeakyBuffer 只定义了两种操作,存和取。
取操作
Get 操作从 LeakyBuffer 所维护的 freelist 的通道中取出一个字节数组,如果 freelist 为空,则说明没有多余的空闲字节数组,则自己make一个字节数组然后返回。
1 | func (lb *LeakyBuffer) Get() (b []byte) { |
存操作
当程序使用完了这段内存空间后,我们将它重新放回 LeakyBuffer 中进行回收而不是用 golang runtime 进行垃圾回收,注意一种特殊情况,就是当 freelist 已经满了,这时我们会执行default case, 也就是什么事情也不干,然后这个无家可归的字节数组就会自动被系统进行垃圾回收,这就是 LeakyBuffer 这个名字中 Leaky 的由来,也就是有可能会泄漏出去。
1 | func (lb *LeakyBuffer) Put(b []byte) { |
上述就是 LeakyBuffer 的实现了,如果下次你遇到需要重复分配很多相同大小的内存空间时,不妨可以使用一下这个编程模型。
Golang 标准库的实现
在 golang 标准库中也实现了类似于 LeakyBuffer 的接口,sync.Pool ,有兴趣的读者不妨阅读一下它的源码,这里就不再重复介绍了。