golang sync.Pool試用說明及注意事項

Go tip 是 Go 語言的實驗分支,包含了不少尚在討論,但頗有可能會加入 stable 分支的特性。「Go tip 在作什麼」(原文地址:What's happening in Go tip)分析總結了 Go 語言尚在開發中的一些重要特性。程序員

本文譯自:What's happening in Go tip (2014-01-10)golang

如今是 2014 年了,剛剛經歷了聖誕和新年前夜,Go 團隊就已經開始爲下一個發佈版本而工做了。也所以,「Go tip 在作什麼」系列也重開了。算法

做爲這個系列的最新一篇,這篇文章將會有些小調整。最重要的調整是,不會再遵循每週一篇文章的發佈週期。一週裏可能有幾篇文章,也可能一篇都沒有。這個調整,一部分因爲我的緣由,一部分也由於這樣能夠更靈活的追蹤 Go 的改變。這樣作的結果是,每篇文章可能會比之前更短,以便能緊跟最新的開發變化。緩存

另外一個調整是,將會覆蓋一些關於沒有變化的代碼的造成緣由和討論。這是由於 Go 1.3 將會有重大改變(主要是計劃用 Go 重寫整個編譯器),有些代碼須要及早被你們瞭解。併發

這篇文章咱們將會關注類型sync.Pool。這個類型是 Go 1.3 標準庫新添加的第一個重要功能。app

作了什麼

  • 添加了sync.Pool類型dom

  • 開發流程的小改變google

添加了sync.Pool類型

相關 CL:CL 41860043CL 43990043CL 37720047CL 44080043CL 44150043CL 44060044CL 44050044,CL 44680043CL 46010043spa

像 JVM 這種項目,花了不少的精力來改進垃圾收集系統,來保證其所要處理回收的衆多垃圾。另外一方面 Go,大體上採用了在第一時間避免垃圾的設計方法,須要一個不那麼時髦的垃圾收集系統,來保證將內存的控制權交還給程序員。設計

因爲這點,標準庫裏一些包分別實現了重用對象的池,來避免產生過多的垃圾。regexp包爲了保證併發時使用同一個正則,而維護了一組狀態機,fmt包有衆多的打印實例,其餘包也有各自的池,或者能夠採用這種技術。

不過,這種方法有兩個問題。最明顯的問題是代碼重複:即使重要的代碼大都相同,全部的包也須要實現一份本身的池。比較細微的問題是,沒有辦法回收池持有的空間。這種簡單的實現歷來不會釋放內存,違反了使用垃圾回收的語言的原則,致使太高但沒必要要的內存使用。

由於這些問題,Brad Fizpatrick曾建議sync包里加入一個公開的Cache類型。這個建議引起了一長串的討論。Go 語言應該在標準庫裏提供一個這個樣子的類型,仍是應當將這個類型做爲私下的實現?這個實現應該真的釋放內存麼?若是釋放,何時釋放?這個類型應當叫作Cache,或者更應該叫作Pool

我先解釋一下緩存(cache)和池(pool)的區別,以及爲何這個區別對討論很重要。Brad Fizpatrick 建議的類型其實是一種池:一組能夠互換的值,取出時並不關心具體的值是什麼,由於每一個值都是剛被初始化的狀態,值是相同的。你甚至分不出來剛剛拿到的值是從池裏取出來的,仍是新建立的。另外一方面,緩存是一些相呼映射的鍵和值。一個明顯的例子是磁盤緩存。磁盤緩存將慢速存儲中的文件緩存在系統主內存裏,以便提升訪問速度。若是緩存裏有對應鍵 A 和 B 的值(磁盤緩存的例子裏,就是文件名),而你請求了與 A 對應的值,你顯然不想獲得 B 所對應的值。實際上,緩存裏的值是互不相同的,增長了緩存清除機制的複雜性,就是說到底哪一個值應該被清除出緩存。維基百科上關於緩存算法的頁面,列舉了 13 種不一樣的清除緩存的算法,從著名的 LRU 緩存到更復雜的好比LIRS 緩存算法

按照這種方式,咱們的池真正要關心的問題,只是何時回收池佔有的空間。並且你們提到了幾乎各類可能性:一些在 GC 前回收,一些在 GC 後,基於時鐘或者採用弱引用指針。全部的建議都有其弊病。

在經歷了漫長的討論後,Russ Cox 最終提議的 API 和回收策略很是簡單:在垃圾收集時回收池空間。這個建議提醒咱們,類型Pool的目的是在垃圾收集之間重用內存。它不該該避免垃圾回收,而是讓垃圾回收變得更有效。

實現了這個提議,並在幾回討論後,提交到 Go 的代碼庫。固然,這個 CL 不是最終結果。首先,全部的池都要改寫爲sync.Pool。這些改寫由CL 43990043CL 37720047CL 44080043CL 44150043CL 44060044追蹤,但包括CL 44050044CL 44050044關注在嘗試將encoding/gob包裏使用的本地釋放鏈表替換爲sync.Pool。本地是個關鍵詞。一個釋放鏈表會和一個解碼器(decoder)的生存時期同樣長,直到這個解碼器被銷燬,纔會釋放這個鏈表。Russ Cox回覆了這個 CL,明確了sync.Pool的目的,以及它不能用來作什麼。直到這時,Rob Pike 提交併回覆了CL 44680043,擴展了sync.Pool類型的文檔,將其目的描述得更清楚。

Pool設計用意是在全局變量裏維護的釋放鏈表,尤爲是被多個 goroutine 同時訪問的全局變量。使用Pool代替本身寫的釋放鏈表,可讓程序運行的時候,在恰當的場景下從池裏重用某項值。sync.Pool一種合適的方法是,爲臨時緩衝區建立一個池,多個客戶端使用這個緩衝區來共享全局資源。另外一方面,若是釋放鏈表是某個對象的一部分,並由這個對象維護,而這個對象只由一個客戶端使用,在這個客戶端工做完成後釋放鏈表,那麼用Pool實現這個釋放鏈表是不合適的。

從回覆(和更早的討論)來看,加入sync.Pool仍是一種試驗,若是Pool沒有實現它的功能,有可能發佈 Go 1.3 以前將其徹底移除。這件事情由Issue 6984跟蹤。

雖然本文對sync.Pool的探索結束了,可是關於池的討論尚未結束。還有CL 46010043,爲了更適合併發時使用,改進了很是簡單的初始化實現。但這個 CL 在目前尚未經過審覈。

開發流程的小改變

從 Go 1.3 的週期開始,開發的流程有一些小的變化。這些變化只會影響到直接參與開發流程的人,以及像我同樣,緊跟最新變更的人。

啓用了一個新的郵件列表,golang-coderreviews,並做爲新 CL 的默認抄送對象,替代了原有的golang-dev。這個想法是爲了下降 golang-dev 裏的噪音,以便讓其關注真正的討論。

同時也啓用了一個新的信息板,容許提交者更容易的跟蹤還在開放的 Issue 和 CL。任何對 Go 團隊的工做方式感興趣的人,均可以在這個新的信息板上找到有用的說明。

相關文章
相關標籤/搜索