go語(yǔ)言的官方包sync.Pool的實(shí)現(xiàn)原理和適用場(chǎng)景

已經(jīng)使用golang有一段時(shí)間偷俭,go的協(xié)程和gc垃圾回收特性的確會(huì)提高程序的開(kāi)發(fā)效率淌哟。但是畢竟是一門(mén)新語(yǔ)言贞绵,如果對(duì)于它的機(jī)制不了解慎冤,用起來(lái)可能會(huì)蹦出各種潘多拉盒子严望。今天就講講我在項(xiàng)目中用到的sync包的Pool類(lèi)的使用村象,以免大家混淆使用。

眾所周知鹅很,go是自動(dòng)垃圾回收的(garbage collector)嘶居,這大大減少了程序編程負(fù)擔(dān)。但gc是一把雙刃劍促煮,帶來(lái)了編程的方便但同時(shí)也增加了運(yùn)行時(shí)開(kāi)銷(xiāo)邮屁,使用不當(dāng)甚至?xí)?yán)重影響程序的性能。因此性能要求高的場(chǎng)景不能任意產(chǎn)生太多的垃圾(有g(shù)c但又不能完全依賴(lài)它挺惡心的)菠齿,如何解決呢佑吝?那就是要重用對(duì)象了,我們可以簡(jiǎn)單的使用一個(gè)chan把這些可重用的對(duì)象緩存起來(lái)绳匀,但如果很多goroutine競(jìng)爭(zhēng)一個(gè)chan性能肯定是問(wèn)題…由于golang團(tuán)隊(duì)認(rèn)識(shí)到這個(gè)問(wèn)題普遍存在芋忿,為了避免大家重造車(chē)輪,因此官方統(tǒng)一出了一個(gè)包Pool襟士。但為什么放到sync包里面也是有的迷惑的盗飒,先不討論這個(gè)問(wèn)題嚷量。

先來(lái)看看如何使用一個(gè)pool:

packagemainimport("fmt""sync")funcmain(){p:=&sync.Pool{New:func()interface{}{return0},}a:=p.Get().(int)p.Put(1)b:=p.Get().(int)fmt.Println(a,b)}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

上面創(chuàng)建了一個(gè)緩存int對(duì)象的一個(gè)pool陋桂,先從池獲取一個(gè)對(duì)象然后放進(jìn)去一個(gè)對(duì)象再取出一個(gè)對(duì)象,程序的輸出是0 1蝶溶。創(chuàng)建的時(shí)候可以指定一個(gè)New函數(shù)嗜历,獲取對(duì)象的時(shí)候如何在池里面找不到緩存的對(duì)象將會(huì)使用指定的new函數(shù)創(chuàng)建一個(gè)返回,如果沒(méi)有new函數(shù)則返回nil抖所。用法是不是很簡(jiǎn)單梨州,我們這里就不多說(shuō),下面來(lái)說(shuō)說(shuō)我們關(guān)心的問(wèn)題:

1田轧、緩存對(duì)象的數(shù)量和期限

上面我們可以看到pool創(chuàng)建的時(shí)候是不能指定大小的暴匠,所有sync.Pool的緩存對(duì)象數(shù)量是沒(méi)有限制的(只受限于內(nèi)存),因此使用sync.pool是沒(méi)辦法做到控制緩存對(duì)象數(shù)量的個(gè)數(shù)的傻粘。另外sync.pool緩存對(duì)象的期限是很詭異的每窖,先看一下src/pkg/sync/pool.go里面的一段實(shí)現(xiàn)代碼:

funcinit(){runtime_registerPoolCleanup(poolCleanup)}

1

2

3

可以看到pool包在init的時(shí)候注冊(cè)了一個(gè)poolCleanup函數(shù)帮掉,它會(huì)清除所有的pool里面的所有緩存的對(duì)象,該函數(shù)注冊(cè)進(jìn)去之后會(huì)在每次gc之前都會(huì)調(diào)用窒典,因此sync.Pool緩存的期限只是兩次gc之間這段時(shí)間蟆炊。例如我們把上面的例子改成下面這樣之后,輸出的結(jié)果將是0 0瀑志。正因gc的時(shí)候會(huì)清掉緩存對(duì)象涩搓,也不用擔(dān)心pool會(huì)無(wú)限增大的問(wèn)題。

a:=p.Get().(int)p.Put(1)runtime.GC()b:=p.Get().(int)fmt.Println(a,b)

1

2

3

4

5

這是很多人錯(cuò)誤理解的地方劈猪,正因?yàn)檫@樣昧甘,我們是不可以使用sync.Pool去實(shí)現(xiàn)一個(gè)socket連接池的。

2战得、緩存對(duì)象的開(kāi)銷(xiāo)

如何在多個(gè)goroutine之間使用同一個(gè)pool做到高效呢疾层?官方的做法就是盡量減少競(jìng)爭(zhēng),因?yàn)閟ync.pool為每個(gè)P(對(duì)應(yīng)cpu贡避,不了解的童鞋可以去看看golang的調(diào)度模型介紹)都分配了一個(gè)子池痛黎,如下圖:

當(dāng)執(zhí)行一個(gè)pool的get或者put操作的時(shí)候都會(huì)先把當(dāng)前的goroutine固定到某個(gè)P的子池上面,然后再對(duì)該子池進(jìn)行操作刮吧。每個(gè)子池里面有一個(gè)私有對(duì)象和共享列表對(duì)象湖饱,私有對(duì)象是只有對(duì)應(yīng)的P能夠訪(fǎng)問(wèn),因?yàn)橐粋€(gè)P同一時(shí)間只能執(zhí)行一個(gè)goroutine杀捻,因此對(duì)私有對(duì)象存取操作是不需要加鎖的井厌。共享列表是和其他P分享的,因此操作共享列表是需要加鎖的致讥。

獲取對(duì)象過(guò)程是:

1)固定到某個(gè)P仅仆,嘗試從私有對(duì)象獲取,如果私有對(duì)象非空則返回該對(duì)象垢袱,并把私有對(duì)象置空墓拜;

2)如果私有對(duì)象是空的時(shí)候,就去當(dāng)前子池的共享列表獲惹肫酢(需要加鎖)咳榜;

3)如果當(dāng)前子池的共享列表也是空的,那么就嘗試去其他P的子池的共享列表偷取一個(gè)(需要加鎖)爽锥;

4)如果其他子池都是空的涌韩,最后就用用戶(hù)指定的New函數(shù)產(chǎn)生一個(gè)新的對(duì)象返回。

可以看到一次get操作最少0次加鎖氯夷,最大N(N等于MAXPROCS)次加鎖臣樱。

歸還對(duì)象的過(guò)程:

1)固定到某個(gè)P,如果私有對(duì)象為空則放到私有對(duì)象;

2)否則加入到該P(yáng)子池的共享列表中(需要加鎖)雇毫。

可以看到一次put操作最少0次加鎖奢啥,最多1次加鎖。

由于goroutine具體會(huì)分配到那個(gè)P執(zhí)行是golang的協(xié)程調(diào)度系統(tǒng)決定的嘴拢,因此在MAXPROCS>1的情況下桩盲,多goroutine用同一個(gè)sync.Pool的話(huà),各個(gè)P的子池之間緩存的對(duì)象是否平衡以及開(kāi)銷(xiāo)如何是沒(méi)辦法準(zhǔn)確衡量的席吴。但如果goroutine數(shù)目和緩存的對(duì)象數(shù)目遠(yuǎn)遠(yuǎn)大于MAXPROCS的話(huà)赌结,概率上說(shuō)應(yīng)該是相對(duì)平衡的。

總的來(lái)說(shuō)孝冒,sync.Pool的定位不是做類(lèi)似連接池的東西柬姚,它的用途僅僅是增加對(duì)象重用的幾率,減少gc的負(fù)擔(dān)庄涡,而開(kāi)銷(xiāo)方面也不是很便宜的量承。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市穴店,隨后出現(xiàn)的幾起案子撕捍,更是在濱河造成了極大的恐慌,老刑警劉巖泣洞,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忧风,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡球凰,警方通過(guò)查閱死者的電腦和手機(jī)狮腿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)呕诉,“玉大人缘厢,你說(shuō)我怎么就攤上這事∷Υ欤” “怎么了贴硫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)捶闸。 經(jīng)常有香客問(wèn)我夜畴,道長(zhǎng)拖刃,這世上最難降的妖魔是什么删壮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮兑牡,結(jié)果婚禮上央碟,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好亿虽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布菱涤。 她就那樣靜靜地躺著,像睡著了一般洛勉。 火紅的嫁衣襯著肌膚如雪粘秆。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天收毫,我揣著相機(jī)與錄音攻走,去河邊找鬼。 笑死此再,一個(gè)胖子當(dāng)著我的面吹牛昔搂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播输拇,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼摘符,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了策吠?” 一聲冷哼從身側(cè)響起逛裤,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猴抹,沒(méi)想到半個(gè)月后别凹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洽糟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年炉菲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坤溃。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拍霜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出薪介,到底是詐尸還是另有隱情祠饺,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布汁政,位于F島的核電站道偷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏记劈。R本人自食惡果不足惜勺鸦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望目木。 院中可真熱鬧换途,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至懈息,卻和暖如春肾档,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辫继。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工阁最, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骇两。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓速种,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親低千。 傳聞我的和親對(duì)象是個(gè)殘疾皇子配阵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容