正如sycn.Pool的名字所示,這是go中實現的一個對象池,為什么要有這個池呢?首先go是自帶垃圾回收機制(也就是通常所說的gc)。gc會帶來運行時的開銷,對于高頻的內存申請與釋放,如果將不用的對象存放在一個池子中,用的時候從池子中取出一個對象,用完了再還回去,這樣就能減輕gc的壓力。
創新互聯建站專注為客戶提供全方位的互聯網綜合服務,包含不限于網站制作、成都網站設計、炎陵網絡推廣、微信小程序定制開發、炎陵網絡營銷、炎陵企業策劃、炎陵品牌公關、搜索引擎seo、人物專訪、企業宣傳片、企業代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們最大的嘉獎;創新互聯建站為所有大學生創業者提供炎陵建站搭建服務,24小時服務熱線:028-86922220,官方網址:m.newbst.com
對于池這個概念,之前可能聽說過連接池。能否用sync.Pool實現一個連接池呢?答案是不能的。因為對于sync.Pool而言,我們無法保證每次放回去再取出來的對象是與之前一致的,對象的內存存在著唄銷毀的可能。因此,這個sync.Pool的存在僅僅是為了減緩gc的壓力而生的。
定義sync.Pool的時候只需要設置一個New成員,它是一個函數,類型為func() interface{},當池子中沒有空閑的對象時就會調用New函數生成一個。由于pool中對象的數量不可控,因此并沒有傳遞任何與對象數量有關的參數。
然后,調用調用Get函數就可以取出一個對象,調用Put函數就可以將對象歸還到池子中。
在go http每一次go serve(l)都會構建Request數據結構。在大量數據請求或高并發的場景中,頻繁創建銷毀對象,會導致GC壓力。解決辦法之一就是使用對象復用技術。在http協議層之下,使用對象復用技術創建Request數據結構。在http協議層之上,可以使用對象復用技術創建(w,*r,ctx)數據結構。這樣即可以回快TCP層讀包之后的解析速度,也可也加快請求處理的速度。
先上一個測試:
結論是這樣的:
貌似使用池化,性能弱爆了???這似乎與net/http使用sync.pool池化Request來優化性能的選擇相違背。這同時也說明了一個問題,好的東西,如果濫用反而造成了性能成倍的下降。在看過pool原理之后,結合實例,將給出正確的使用方法,并給出預期的效果。
sync.Pool是一個 協程安全 的 臨時對象池 。數據結構如下:
local 成員的真實類型是一個 poolLocal 數組,localSize 是數組長度。這涉及到Pool實現,pool為每個P分配了一個對象,P數量設置為runtime.GOMAXPROCS(0)。在并發讀寫時,goroutine綁定的P有對象,先用自己的,沒有去偷其它P的。go語言將數據分散在了各個真正運行的P中,降低了鎖競爭,提高了并發能力。
不要習慣性地誤認為New是一個關鍵字,這里的New是Pool的一個字段,也是一個閉包名稱。其API:
如果不指定New字段,對象池為空時會返回nil,而不是一個新構建的對象。Get()到的對象是隨機的。
原生sync.Pool的問題是,Pool中的對象會被GC清理掉,這使得sync.Pool只適合做簡單地對象池,不適合作連接池。
pool創建時不能指定大小,沒有數量限制。pool中對象會被GC清掉,只存在于兩次GC之間。實現是pool的init方法注冊了一個poolCleanup()函數,這個方法在GC之前執行,清空pool中的所有緩存對象。
為使多協程使用同一個POOL。最基本的想法就是每個協程,加鎖去操作共享的POOL,這顯然是低效的。而進一步改進,類似于ConcurrentHashMap(JDK7)的分Segment,提高其并發性可以一定程度性緩解。
注意到pool中的對象是無差異性的,加鎖或者分段加鎖都不是較好的做法。go的做法是為每一個綁定協程的P都分配一個子池。每個子池又分為私有池和共享列表。共享列表是分別存放在各個P之上的共享區域,而不是各個P共享的一塊內存。協程拿自己P里的子池對象不需要加鎖,拿共享列表中的就需要加鎖了。
Get對象過程:
Put過程:
如何解決Get最壞情況遍歷所有P才獲取得對象呢:
方法1止前sync.pool并沒有這樣的設置。方法2由于goroutine被分配到哪個P由調度器調度不可控,無法確保其平衡。
由于不可控的GC導致生命周期過短,且池大小不可控,因而不適合作連接池。僅適用于增加對象重用機率,減少GC負擔。2
執行結果:
單線程情況下,遍歷其它無元素的P,長時間加鎖性能低下。啟用協程改善。
結果:
測試場景在goroutines遠大于GOMAXPROCS情況下,與非池化性能差異巨大。
測試結果
可以看到同樣使用*sync.pool,較大池大小的命中率較高,性能遠高于空池。
結論:pool在一定的使用條件下提高并發性能,條件1是協程數遠大于GOMAXPROCS,條件2是池中對象遠大于GOMAXPROCS。歸結成一個原因就是使對象在各個P中均勻分布。
池pool和緩存cache的區別。池的意思是,池內對象是可以互換的,不關心具體值,甚至不需要區分是新建的還是從池中拿出的。緩存指的是KV映射,緩存里的值互不相同,清除機制更為復雜。緩存清除算法如LRU、LIRS緩存算法。
池空間回收的幾種方式。一些是GC前回收,一些是基于時鐘或弱引用回收。最終確定在GC時回收Pool內對象,即不回避GC。用java的GC解釋弱引用。GC的四種引用:強引用、弱引用、軟引用、虛引用。虛引用即沒有引用,弱引用GC但有空間則保留,軟引用GC即清除。ThreadLocal的值為弱引用的例子。
regexp 包為了保證并發時使用同一個正則,而維護了一組狀態機。
fmt包做字串拼接,從sync.pool拿[]byte對象。避免頻繁構建再GC效率高很多。
1.在創建連接池之后,起一個 go routine,每隔一段 idleTime 發送一個 PING 到 Redis server。其中,idleTime 略小于 Redis server 的 timeout 配置。
2.連接池初始化部分代碼如下:
p, err := pool.New("tcp", u.Host, concurrency) errHndlr(err) go func() { for { p.Cmd("PING") time.Sleep(idelTime * time.Second) } }()
3.使用 redis 傳輸數據部分代碼如下:
func redisDo(p *pool.Pool, cmd string, args ...interface{}) (reply *redis.Resp, err error) { reply = p.Cmd(cmd, args...) if err = reply.Err; err != nil { if err != io.EOF { Fatal.Println("redis", cmd, args, "err is", err) } } return }
4.其中,Radix.v2 連接池內部進行了連接池內連接的獲取和放回,代碼如下:
// Cmd automatically gets one client from the pool, executes the given command // (returning its result), and puts the client back in the pool func (p *Pool) Cmd(cmd string, args ...interface{}) *redis.Resp { c, err := p.Get() if err != nil { return redis.NewResp(err) } defer p.Put(c) return c.Cmd(cmd, args...) }
這樣,就有了系統 keep alive 的機制,不會出現 time out 的連接了,從 redis 連接池里面取出的連接都是可用的連接了。看似簡單的代碼,卻完美的解決了連接池里面超時連接的問題。同時,就算 Redis server 重啟等情況,也能保證連接自動重連。
本文名稱:poolgo語言,poor pool發音
分享鏈接:http://m.newbst.com/article22/dssgdcc.html
成都網站建設公司_創新互聯,為您提供網站建設、響應式網站、品牌網站建設、關鍵詞優化、做網站、定制開發
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯