当前位置: 首页 > 科技观察

去语言-基于Channel

时间:2023-03-16 00:32:40 科技观察

byteslice的并发安全字节池[]byte是我们在编码中经常使用的,比如读取文件内容,或者从io.Reader获取数据等。[]byte是需要的缓冲。funcReadFull(rReader,buf[]byte)(nint,errerror)func(f*File)Read(b[]byte)(nint,errerror)以上是两个使用[]byte作为缓冲区的方法.那么现在的问题是,如果我们对上面的方法的调用很多,那么我们就要声明很多[]byte,这样就需要过多的内存申请和释放,GC也会过多。MinIO的字节池这时候我们就需要复用创建的[]byte来提高对象的使用率,减少内存申请和GC。这时候我们可以使用sync.Pool来实现,但是最近在研究开源项目MinIO的时候,发现他们是使用channel的方式来实现字节池的。typeBytePoolCapstruct{cchan[]bytewintwcapint}BytePoolCap结构的定义比较简单,一共有三个字段:c是一个chan,用来充当字节缓冲池w指的是使用make函数创建[]bytelen参数wcap是指使用make函数创建[]byte时的cap参数。通过BytePoolCap结构,可以为其定义Get方法,获取一个缓存的[]byte。func(bp*BytePoolCap)Get()(b[]byte){select{caseb=<-bp.c://重用现有缓冲区default://如果bp.wcap>0{b=make([]byte,bp.w,bp.wcap)}else{b=make([]byte,bp.w)}}return}以上就是经典的select+chan方法,如果能拿到[]byte缓存获取,获取不到就执行default分支,使用make函数生成一个[]byte。从这里也可以看出,结构体中定义的w和wcap字段是make函数的len和cap参数使用的。有了Get方法,还有Put方法,可以把用过的[]byte放回字节池,方便复用。func(bp*BytePoolCap)Put(b[]byte){select{casebp.c<-b://缓冲区返回到池中default://缓冲区没有返回到池中,只是丢弃}}Put方法也是用select+chan,能放就放,不能放就丢弃这个[]byte。定义BytePoolCap后,即可使用Get和Put。在使用之前,BytePoolCap还定义了一个工厂函数用于生成*BytePoolCap,比较方便。funcNewBytePoolCap(maxSizeint,widthint,capwidthint)(bp*BytePoolCap){return&BytePoolCap{c:make(chan[]byte,maxSize),w:width,wcap:capwidth,}}暴露相关参数,即可由调用者自定义。这里的maxSize表示要创建的chan的大小,即字节池的大小和最大存储量。bp:=bpool.NewBytePoolCap(500,1024,1024)buf:=bp.Get()deferbp.Put(buf)//使用buf,不再举例上面是使用字节池的一般套路,记得放它在使用后重新使用。与sync.Pool相比,两者原理基本相似,都是多协程安全的。sync.Pool可以存储任何对象,而BytePoolCap只能存储[]byte,但是由于它的自定义,存储的对象类型清晰,不需要一层类型断言转换,还可以自定义对象池的大小,等关于两者的性能,我做了一个Benchmark测试。总的来说,MinIO的BytePoolCap更胜一筹。varbp=bpool.NewBytePoolCap(500,1024,1024)varsp=&sync.Pool{New:func()interface{}{returnmake([]byte,1024,1024)},}模拟两个字节池,长度[]byte和capacity都是1024。然后就是两个字节池的模拟使用。这里我启动了500个协程来模拟并发。如果不模拟并发,BytePoolCap是一个[]byte的分配,完全把sync.Pool干掉,对sync.Pool不公平。funcopBytePool(bp*bpool.BytePoolCap){varwgsync.WaitGroupwg.Add(500)fori:=0;我<500;i++{gofunc(bp*bpool.BytePoolCap){buffer:=bp.Get()deferbp.Put(buffer)mockReadFile(buffer)wg.Done()}(bp)}wg.Wait()}funcopSyncPool(sp*sync.Pool){varwgsync.WaitGroupwg.Add(500)fori:=0;我<500;i++{gofunc(sp*sync.Pool){buffer:=sp.Get().([]byte)defersp.Put(buffer)mockReadFile(buffer)wg.Done()}(sp)}wg.Wait复制代码()}接下来是我模拟读取本地文件的函数mockReadFile(buffer):funcmockReadFile(b[]byte){f,_:=os.Open("water")for{n,err:=io.ReadFull(f,b)如果n==0||err==io.EOF{break}}}然后运行??gotest-bench=。-benchmem-run=none查看测试结果:pkg:flysnow.org/helloBenchmarkBytePool-81489979113ns/op36504B/op1152allocs/opBenchmarkSyncPool-810081172429ns/op57788B/op1744allocs/op从测试结果可以看出,BytePoolCap是在内存中分配的,每次操作分配字节,每次操作耗时都比sync.Pool小结ofadvantages很多优秀的开源项目都能看到很多优秀的源码实现,会根据自己的业务场景做出更好的优化。本文转载自微信公众号“飞雪无情”,可通过以下二维码关注。转载本文请联系飞雪无情公众号。