如何泄漏一個協程然後修複它

來源:互聯網
上載者:User
很多 go 語言開發人員都知道這句格言,[永遠不要啟動一個你不知道如何停止的協程](https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop),但是泄漏一個協程還是超級的簡單。讓我們看一種常碰到的泄漏協程的方式,然後修複它。為了實現這個,我們先建立一個包含一個自訂 `map` 類型的庫,這個 `map` 類型的 key 在經過了一段可配置的時間後到期。我們把這個庫叫做 [ttl](https://en.wikipedia.org/wiki/Time_to_live) ,這個庫有一個 `API` 類似如下:```go// 建立一個生存周期為5分鐘的mapm := ttl.NewMap(5*time.Minute)//設定一個keym.Set("my-key", []byte("my-value"))// 讀取一個keyv, ok := m.Get("my-key")//得到 "my-value"fmt.Println(string(v))// true, key存在fmt.Println(ok)// ... 過了5分鐘之後v, ok := m.Get("my-key")// 沒有值fmt.Println(string(v) == "")// false, key已經到期了fmt.Println(ok)```為了確保key會到期,我們在NewMap函數中啟動一個協程。```gofunc NewMap(expiration time.Duration) *Map {m := &Map{data: make(map[string]expiringValue),expiration: expiration,}// start a worker goroutinego func() {for range time.Tick(expiration) {m.removeExpired()}}()return m}```這個工作協程每運行一段配置好的時間後會在這個 `map` 上調用一個方法來刪除到期的 `key`。這意味著 `SetKey` 方法必須記錄 `key` 的進入時間,這也是為什麼 `data` 欄位包含一個 `expiringValue` 類型,這個類型與一個記錄實際到期時間的值相關聯:```gotype expiringValue struct {expiration time.Timedata []byte //實際的值}```對於不敏感的人來說,這個工作協程的調用看起來沒問題,並且如果這不是一篇關於協程泄漏的文章,掃一眼這幾行代碼並沒有什麼讓人覺得驚奇的地方,雖然如此,我們還是在構造器中漏泄了一個協程,問題,怎麼泄漏的?我們回顧一下 `map` 類型的生命週期。首先,一個調用者建立了一個 `map` 的執行個體,在建立執行個體後,一個工作協程開始運行。接下來調用者可能調用若干次 `Set` 和 `Get` 方法,最終,調用會結束使用這個 `map` 的執行個體,並且釋放所有對它的引用。這時,垃圾收集器應該會正常的回收這個執行個體對應的記憶體。然而,工作協程還在運行並且還擁有一個對這個 map 執行個體的引用。因為並沒有其他顯示的調用來停止這個協程,我我們就把這個協程已經使用的記憶體給泄漏了。讓我們把這個問題再說的明白一點。我們使用 `runtime` 包來查看記憶體收集器和啟動並執行協程在某一時刻的統計資料```gofunc main() {go func() {var stats runtime.MemStatsfor {runtime.ReadMemStats(&stats)fmt.Printf("HeapAlloc = %d\n", stats.HeapAlloc)fmt.Printf("NumGoroutine = %d\n", runtime.NumGoroutine())time.Sleep(5*time.Second)}}()for {work()}}func work() {m := ttl.NewMap(5*time.Minute)m.Set("my-key", []byte("my-value"))if _, ok := m.Get("my-key"); !ok {panic("no value present")}// m超出變數範圍}```不用很長時間,我們就可以看到分配的堆記憶體和啟動並執行協程數增長得非常,非常的快。```HeapAlloc = 76960NumGoroutine = 18HeapAlloc = 2014278208NumGoroutine = 1447847HeapAlloc = 3932578560NumGoroutine = 2832416HeapAlloc = 5926163224NumGoroutine = 4322524```很明顯,我們現在得停止那些協程。目前在 `Map` 上提供的 `API` 中並沒有辦法停止這個工作協程,如果不改變任何的 API 但是仍然能在調用者使用完 `map` 執行個體時停止工作協程,那是很理想的。但是只有調用者知道什麼時候完了 `map` 執行個體。一個常用的解決這個方法的模式是實現一個 `io.Closer` 介面,當調用者用完了 `map` 執行個體,調用一下 `Close` 方法告訴 `Map` 停止它的工作協程。```gofunc (m *Map) Close() error {close(m.done)return nil}```在我們的構造器中工作協程的調用會看起來類似這樣:```gofunc NewMap(expiration time.Duration) *Map {m := &Map{data: make(map[string]expiringValue),expiration: expiration,done: make(chan struct{}),}// 啟動一個工作協程go func() {ticker := time.NewTicker(expiration)defer ticker.Stop()for {select {case <-ticker.C:m.removeExpired()case <-m.done:return}}}()return m}```現在工作協程包含了一個 `select` 語句,它會檢查 `done通道` 也會檢查 `ticker 的通道`,主要的,我們還刪除了 [time.Tick](https://godoc.org/time#Tick),因為它並不能讓協程順利關閉還是會造成泄漏。經過以上的修改,我們簡化的統計資料看起像這樣:```HeapAlloc = 72464NumGoroutine = 6HeapAlloc = 5175200NumGoroutine = 59HeapAlloc = 5495008NumGoroutine = 35HeapAlloc = 9171136NumGoroutine = 240HeapAlloc = 8347120NumGoroutine = 53```這些數字都非常小,這是因為 `work` 在一個很小的迴圈中被調用,更重要的是,我們不再看到協程數或者分配的堆記憶體的飛速增長,這就是我們想要的結果。注意,最終的代碼可以在[**這兒**](https://github.com/gobuildit/gobuildit/tree/master/ttl)找到。這篇文章提供了一個為什麼知道一個協程何時停止這麼的重要的樣本,同時,也提醒我們,監控一個程式中啟動並執行協程數也是非常重要的。這樣的監控可以給代碼中隱藏的協程泄漏提供一個警示。同時我們也要牢記,有時候協程泄漏需要幾天甚至幾周才發生。因此對應用程式同時進行短期和長期的監控是非常值得的。多謝 Jean de Kelerk 和 Jason Keene,他們讀了這篇文章的草稿。

via: https://commandercoriander.net/blog/2018/05/22/how-to-leak-a-goroutine-then-fix-it/

作者:Eno Compton 譯者:MoodWu 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

283 次點擊  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.