日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

golang对象池

發(fā)布時(shí)間:2025/6/15 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 golang对象池 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

簡介

  • sync.Pool是一個(gè)可以存或取的臨時(shí)對(duì)象集合

  • sync.Pool可以安全被多個(gè)線程同時(shí)使用,保證線程安全

  • 注意、注意、注意,sync.Pool中保存的任何項(xiàng)都可能隨時(shí)不做通知的釋放掉,所以不適合用于像socket長連接或數(shù)據(jù)庫連接池。

  • sync.Pool主要用途是增加臨時(shí)對(duì)象的重用率,減少GC負(fù)擔(dān)

關(guān)于堆和棧

程序會(huì)從操作系統(tǒng)申請(qǐng)一塊內(nèi)存,而這塊內(nèi)存也會(huì)被分成堆和棧。棧可以簡單得理解成一次函數(shù)調(diào)用內(nèi)部申請(qǐng)到的內(nèi)存,它們會(huì)隨著函數(shù)的返回把內(nèi)存還給系統(tǒng)。

func F() {temp := make([]int, 0, 20)... }

類似于上面代碼里面的temp變量,只是內(nèi)函數(shù)內(nèi)部申請(qǐng)的臨時(shí)變量,并不會(huì)作為返回值返回,它就是被編譯器申請(qǐng)到棧里面。申請(qǐng)到棧內(nèi)存好處:函數(shù)返回直接釋放,不會(huì)引起垃圾回收,對(duì)性能沒有影響。

func F() []int{a := make([]int, 0, 20)return a }

而上面這段代碼,申請(qǐng)的代碼一模一樣,但是申請(qǐng)后作為返回值返回了,編譯器會(huì)認(rèn)為變量之后還會(huì)被使用,當(dāng)函數(shù)返回之后并不會(huì)將其內(nèi)存歸還,那么它就會(huì)被申請(qǐng)到堆上面了。申請(qǐng)到堆上面的內(nèi)存才會(huì)引起垃圾回收。

func F() {a := make([]int, 0, 20)b := make([]int, 0, 20000)l := 20c := make([]int, 0, l) }

a和b代碼一樣,就是申請(qǐng)的空間不一樣大,但是它們兩個(gè)的命運(yùn)是截然相反的。a前面已經(jīng)介紹過,會(huì)申請(qǐng)到棧上面,而b,由于申請(qǐng)的內(nèi)存較大,編譯器會(huì)把這種申請(qǐng)內(nèi)存較大的變量轉(zhuǎn)移到堆上面。即使是臨時(shí)變量,申請(qǐng)過大也會(huì)在堆上面申請(qǐng)。

而c,對(duì)我們而言其含義和a是一致的,但是編譯器對(duì)于這種不定長度的申請(qǐng)方式,也會(huì)在堆上面申請(qǐng),即使申請(qǐng)的長度很短。

實(shí)際項(xiàng)目基本都是通過c := make([]int, 0, l)來申請(qǐng)內(nèi)存,長度都是不確定的。自然而然這些變量都會(huì)申請(qǐng)到堆上面了

簡單得說,就是程序要從操作系統(tǒng)申請(qǐng)一塊比較大的內(nèi)存,內(nèi)存分成小塊,通過鏈表鏈接。每次程序申請(qǐng)內(nèi)存,就從鏈表上面遍歷每一小塊,找到符合的就返回其地址,沒有合適的就從操作系統(tǒng)再申請(qǐng)。如果申請(qǐng)內(nèi)存次數(shù)較多,而且申請(qǐng)的大小不固定,就會(huì)引起內(nèi)存碎片化的問題。申請(qǐng)的堆內(nèi)存并沒有用完,但是用戶申請(qǐng)的內(nèi)存的時(shí)候卻沒有合適的空間提供。這樣會(huì)遍歷整個(gè)鏈表,還會(huì)繼續(xù)向操作系統(tǒng)申請(qǐng)內(nèi)存。這就能解釋我一開始描述的問題,申請(qǐng)一塊內(nèi)存變成了慢語句。

申請(qǐng)內(nèi)存變成了慢語句,解決方法就是使用臨時(shí)對(duì)象池

臨時(shí)對(duì)象池

如何解決這個(gè)問題,首先想到的就是對(duì)象池。Golang在sync里面提供了對(duì)象池Pool。一般大家都叫這個(gè)為對(duì)象池,而我喜歡叫它臨時(shí)對(duì)象池。因?yàn)槊看卫厥諘?huì)把池子里面不被引用的對(duì)象回收掉。

func (p *Pool) Get() interface{}

需要注意的是,Get方法會(huì)把返回的對(duì)象從池子里面刪除。所以用完了的對(duì)象,還是得重新放回池子

package mainimport ("fmt""sync""time" )// 一個(gè)[]byte的對(duì)象池,每個(gè)對(duì)象為一個(gè)[]byte var bytePool = sync.Pool{New: func() interface{} {b := make([]byte, 1024)return &b}, }func main() {a := time.Now().Unix()// 不使用對(duì)象池for i := 0; i < 1000000000; i++ {obj := make([]byte, 1024)_ = obj}b := time.Now().Unix()// 使用對(duì)象池for i := 0; i < 1000000000; i++ {obj := bytePool.Get().(*[]byte)bytePool.Put(obj)}c := time.Now().Unix()fmt.Println("without pool ", b-a, "s")fmt.Println("with pool ", c-b, "s") }

輸出

without pool 20 s with pool 15 s package mainimport ("fmt""sync" )// 一個(gè)[]byte的對(duì)象池,每個(gè)對(duì)象為一個(gè)[]byte var bytePool = sync.Pool{New: func() interface{} {b := make([]byte, 8)return &b}, }func main() {fmt.Printf("%T\n", bytePool)fmt.Printf("%+v\n", bytePool)obj := bytePool.Get().(*[]byte)fmt.Printf("%T\n", obj)fmt.Printf("%v\n", obj) }

輸出

sync.Pool {noCopy:{} local:<nil> localSize:0 New:0x1090180} *[]uint8 &[0 0 0 0 0 0 0 0]

何時(shí)使用pool

只有當(dāng)每個(gè)對(duì)象占用內(nèi)存較大時(shí)候,用pool才會(huì)改善性能

對(duì)比1(起步階段):

package mainimport ("fmt""sync""time" )// 一個(gè)[]byte的對(duì)象池,每個(gè)對(duì)象為一個(gè)[]byte var bytePool = sync.Pool{New: func() interface{} {b := make([]byte, 1)return &b}, }func main() {a := time.Now().Unix()// 不使用對(duì)象池for i := 0; i < 1000000000; i++ {obj := make([]byte, 1)_ = obj}b := time.Now().Unix()// 使用對(duì)象池for i := 0; i < 1000000000; i++ {obj := bytePool.Get().(*[]byte)bytePool.Put(obj)}c := time.Now().Unix()fmt.Println("without pool ", b-a, "s")fmt.Println("with pool ", c-b, "s") }

輸出

without pool 0 s with pool 17 s

可以看到,當(dāng)[]byte只有1個(gè)元素時(shí)候,用pool性能反而更差

對(duì)比2(追趕階段):

package mainimport ("fmt""sync""time" )// 一個(gè)[]byte的對(duì)象池,每個(gè)對(duì)象為一個(gè)[]byte var bytePool = sync.Pool{New: func() interface{} {b := make([]byte, 800)return &b}, }func main() {a := time.Now().Unix()// 不使用對(duì)象池for i := 0; i < 1000000000; i++ {obj := make([]byte, 800)_ = obj}b := time.Now().Unix()// 使用對(duì)象池for i := 0; i < 1000000000; i++ {obj := bytePool.Get().(*[]byte)bytePool.Put(obj)}c := time.Now().Unix()fmt.Println("without pool ", b-a, "s")fmt.Println("with pool ", c-b, "s") }

輸出

without pool 16 s with pool 17 s

可以看到,飛機(jī)快趕上跑車了

對(duì)比3(超越階段):

package mainimport ("fmt""sync""time" )// 一個(gè)[]byte的對(duì)象池,每個(gè)對(duì)象為一個(gè)[]byte var bytePool = sync.Pool{New: func() interface{} {b := make([]byte, 8000)return &b}, }func main() {a := time.Now().Unix()// 不使用對(duì)象池for i := 0; i < 1000000000; i++ {obj := make([]byte, 8000)_ = obj}b := time.Now().Unix()// 使用對(duì)象池for i := 0; i < 1000000000; i++ {obj := bytePool.Get().(*[]byte)bytePool.Put(obj)}c := time.Now().Unix()fmt.Println("without pool ", b-a, "s")fmt.Println("with pool ", c-b, "s") }

輸出

without pool 128 s with pool 17 s

可以看到2個(gè)特征

  • 當(dāng)每個(gè)對(duì)象的內(nèi)存小于一定量的時(shí)候,不使用pool的性能秒殺使用pool;當(dāng)內(nèi)存處于某個(gè)量的時(shí)候,不使用pool和使用pool性能相當(dāng);當(dāng)內(nèi)存大于某個(gè)量的時(shí)候,使用pool的優(yōu)勢就顯現(xiàn)出來了

  • 不使用pool,那么對(duì)象占用內(nèi)存越大,性能下降越厲害;使用pool,無論對(duì)象占用內(nèi)存大還是小,性能都保持不變。可以看到pool有點(diǎn)像飛機(jī),雖然起步比跑車慢,但后勁十足。

  • 即:pool適合占用內(nèi)存大且并發(fā)量大的場景。當(dāng)內(nèi)存小并發(fā)量少的時(shí)候,使用pool適得其反

    知識(shí)點(diǎn)

    package mainimport ("fmt""sync" )// 一個(gè)[]int的對(duì)象池,每個(gè)對(duì)象為一個(gè)[]int var intPool = sync.Pool{New: func() interface{} {b := make([]int, 8)return &b}, }func main() {// 不使用對(duì)象池for i := 1; i < 3; i++ {obj := make([]int, 8)obj[i] = ifmt.Printf("obj%d: %T %+v\n", i, obj, obj)}fmt.Println("-----------")// 使用對(duì)象池for i := 1; i < 3; i++ {obj := intPool.Get().(*[]int)(*obj)[i] = ifmt.Printf("obj%d: %T %+v\n", i, obj, obj)intPool.Put(obj)} }

    輸出

    obj1: []int [0 1 0 0 0 0 0 0] obj2: []int [0 0 2 0 0 0 0 0] ----------- obj1: *[]int &[0 1 0 0 0 0 0 0] obj2: *[]int &[0 1 2 0 0 0 0 0]

    可以看到,pool的Get和Put真的是從池里獲得和放入池里,否則不會(huì)出現(xiàn)Get獲得的變量是舊的變量(即之前通過Put放入的)

    如果把上面代碼中的intPool.Put(obj)這行刪掉,那么輸出就是

    obj1: []int [0 1 0 0 0 0 0 0] obj2: []int [0 0 2 0 0 0 0 0] ----------- obj1: *[]int &[0 1 0 0 0 0 0 0] obj2: *[]int &[0 0 2 0 0 0 0 0]
  • Pool的目的是緩存已分配但未使用的項(xiàng)目以備后用

  • 多協(xié)程并發(fā)安全

  • 緩存在Pool里的item會(huì)沒有任何通知情況下隨時(shí)被移除,以緩解GC壓力

  • 池提供了一種方法來緩解跨多個(gè)客戶端的分配開銷。

  • 不是所有場景都適合用Pool,如果釋放鏈表是某個(gè)對(duì)象的一部分,并由這個(gè)對(duì)象維護(hù),而這個(gè)對(duì)象只由一個(gè)客戶端使用,在這個(gè)客戶端工作完成后釋放鏈表,那么用Pool實(shí)現(xiàn)這個(gè)釋放鏈表是不合適的。

  • 官方對(duì)Pool的目的描述:

    Pool設(shè)計(jì)用意是在全局變量里維護(hù)的釋放鏈表,尤其是被多個(gè) goroutine 同時(shí)訪問的全局變量。使用Pool代替自己寫的釋放鏈表,可以讓程序運(yùn)行的時(shí)候,在恰當(dāng)?shù)膱鼍跋聫某乩镏赜媚稠?xiàng)值。sync.Pool一種合適的方法是,為臨時(shí)緩沖區(qū)創(chuàng)建一個(gè)池,多個(gè)客戶端使用這個(gè)緩沖區(qū)來共享全局資源。另一方面,如果釋放鏈表是某個(gè)對(duì)象的一部分,并由這個(gè)對(duì)象維護(hù),而這個(gè)對(duì)象只由一個(gè)客戶端使用,在這個(gè)客戶端工作完成后釋放鏈表,那么用Pool實(shí)現(xiàn)這個(gè)釋放鏈表是不合適的。

    Pool的正確用法

    在Put之前重置,在Get之后重置

    總結(jié)

    以上是生活随笔為你收集整理的golang对象池的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。