婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av

主頁 > 知識庫 > Go 防止 goroutine 泄露的方法

Go 防止 goroutine 泄露的方法

熱門標簽:南通如皋申請開通400電話 江西轉(zhuǎn)化率高的羿智云外呼系統(tǒng) 地圖標注的汽車標 學海導航地圖標注 西部云谷一期地圖標注 廣州呼叫中心外呼系統(tǒng) 高德地圖標注口訣 浙江高速公路地圖標注 中國地圖標注省會高清

概述

Go 的并發(fā)模型與其他語言不同,雖說它簡化了并發(fā)程序的開發(fā)難度,但如果不了解使用方法,常常會遇到 goroutine 泄露的問題。雖然 goroutine 是輕量級的線程,占用資源很少,但如果一直得不到釋放并且還在不斷創(chuàng)建新協(xié)程,毫無疑問是有問題的,并且是要在程序運行幾天,甚至更長的時間才能發(fā)現(xiàn)的問題。

對于上面描述的問題,我覺得可以從兩方面入手解決,如下:

一是預防,要做到預防,我們就需要了解什么樣的代碼會產(chǎn)生泄露,以及了解如何寫出正確的代碼;

二是監(jiān)控,雖說預防減少了泄露產(chǎn)生的概率,但沒有人敢說自己不犯錯,因而,通常我們還需要一些監(jiān)控手段進一步保證程序的健壯性;

接下來,我將會分兩篇文章分別從這兩個角度進行介紹,今天先談第一點。

如何監(jiān)控泄露

本文主要集中在第一點上,但為了更好的演示效果,可以先介紹一個最簡單的監(jiān)控方式。通過 runtime.NumGoroutine() 獲取當前運行中的 goroutine 數(shù)量,通過它確認是否發(fā)生泄漏。它的使用非常簡單,就不為它專門寫個例子了。

一個簡單的例子

語言級別的并發(fā)支持是 Go 的一大優(yōu)勢,但這個優(yōu)勢也很容易被濫用。通常我們在開始 Go 并發(fā)學習時,常常聽別人說,Go 的并發(fā)非常簡單,在調(diào)用函數(shù)前加上 go 關鍵詞便可啟動 goroutine,即一個并發(fā)單元,但很多人可能只聽到了這句話,然后就出現(xiàn)了類似下面的代碼:

package main
import (
 "fmt"
 "runtime"
 "time"
)
func sayHello() {
 for {
 fmt.Println("Hello gorotine")
 time.Sleep(time.Second)
 }
}
func main() {
 defer func() {
 fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
 }()
 go sayHello()
 fmt.Println("Hello main")
}

對 Go 比較熟悉的話,很容易發(fā)現(xiàn)這段代碼的問題,sayHello 是個死循環(huán),沒有如何退出機制,因此也就沒有任何辦法釋放創(chuàng)建的 goroutine。我們通過在 main 函數(shù)最前面的 defer 實現(xiàn)在函數(shù)退出時打印當前運行中的 goroutine 數(shù)量,毫無意外,它的輸出如下:

the number of goroutines: 2

不過,因為上面的程序并非常駐,有泄露問題也不大,程序退出后系統(tǒng)會自動回收運行時資源。但如果這段代碼在常駐服務中執(zhí)行,比如 http server,每接收到一個請求,便會啟動一次 sayHello,時間流逝,每次啟動的 goroutine 都得不到釋放,你的服務將會離奔潰越來越近。

這個例子比較簡單,我相信,對 Go 的并發(fā)稍微有點了解的朋友都不會犯這個錯。

泄露情況分類

前面介紹的例子由于在 goroutine 運行死循環(huán)導致的泄露。接下來,我會按照并發(fā)的數(shù)據(jù)同步方式對泄露的各種情況進行分析。簡單可歸于兩類,即:

  • channel 導致的泄露
  • 傳統(tǒng)同步機制導致的泄露

傳統(tǒng)同步機制主要指面向共享內(nèi)存的同步機制,比如排它鎖、共享鎖等。這兩種情況導致的泄露還是比較常見的。go 由于 defer 的存在,第二類情況,一般情況下還是比較容易避免的。

chanel 引起的泄露

先說 channel,如果之前讀過官方的那篇并發(fā)的文章[1],翻譯版[2],你會發(fā)現(xiàn) channel 的使用,一個不小心就泄露了。我們來具體總結下那些情況下可能導致。

發(fā)送不接收

我們知道,發(fā)送者一般都會配有相應的接收者。理想情況下,我們希望接收者總能接收完所有發(fā)送的數(shù)據(jù),這樣就不會有任何問題。但現(xiàn)實是,一旦接收者發(fā)生異常退出,停止繼續(xù)接收上游數(shù)據(jù),發(fā)送者就會被阻塞。這個情況在 前面說的文章[3] 中有非常細致的介紹。

示例代碼:

package main
import "time"
func gen(nums ...int) -chan int {
 out := make(chan int)
 go func() {
 for _, n := range nums {
  out - n
 }
 close(out)
 }()
 return out
}
func main() {
 defer func() {
 fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
 }()
 // Set up the pipeline.
 out := gen(2, 3)
 for n := range out {
 fmt.Println(n)  // 2
 time.Sleep(5 * time.Second) // done thing, 可能異常中斷接收
 if true { // if err != nil
  break
 }
 }
}

例子中,發(fā)送者通過 out chan 向下游發(fā)送數(shù)據(jù),main 函數(shù)接收數(shù)據(jù),接收者通常會依據(jù)接收到的數(shù)據(jù)做一些具體的處理,這里用 Sleep 代替。如果這期間發(fā)生異常,導致處理中斷,退出循環(huán)。gen 函數(shù)中啟動的 goroutine 并不會退出。

如何解決?

此處的主要問題在于,當接收者停止工作,發(fā)送者并不知道,還在傻傻地向下游發(fā)送數(shù)據(jù)。故而,我們需要一種機制去通知發(fā)送者。我直接說答案吧,就不循漸進了。Go 可以通過 channel 的關閉向所有的接收者發(fā)送廣播信息。

修改后的代碼:

package main
import "time"
func gen(done chan struct{}, nums ...int) -chan int {
 out := make(chan int)
 go func() {
 defer close(out)
 for _, n := range nums {
  select {
  case out - n:
  case -done:
  return
  }
 }
 }()
 return out
}
func main() {
 defer func() {
 time.Sleep(time.Second)
 fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
 }()
 // Set up the pipeline.
 done := make(chan struct{})
 defer close(done)
 out := gen(done, 2, 3)
 for n := range out {
 fmt.Println(n) // 2
 time.Sleep(5 * time.Second) // done thing, 可能異常中斷接收
 if true { // if err != nil
  break
 }
 }
}

函數(shù) gen 中通過 select 實現(xiàn) 2 個 channel 的同時處理。當異常發(fā)生時,將進入 -done 分支,實現(xiàn) goroutine 退出。這里為了演示效果,保證資源順利釋放,退出時等待了幾秒保證釋放完成。

執(zhí)行后的輸出如下:

the number of goroutines:  1

現(xiàn)在只有主 goroutine 存在。

接收不發(fā)送

發(fā)送不接收會導致發(fā)送者阻塞,反之,接收不發(fā)送也會導致接收者阻塞。直接看示例代碼,如下:

package main

func main() {
 defer func() {
 time.Sleep(time.Second)
 fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
 }()

 var ch chan struct{}
 go func() {
 ch - struct{}{}
 }()
}

運行結果顯示:

the number of goroutines:  2

當然,我們正常不會遇到這么傻的情況發(fā)生,現(xiàn)實工作中的案例更多可能是發(fā)送已完成,但是發(fā)送者并沒有關閉 channel,接收者自然也無法知道發(fā)送完畢,阻塞因此就發(fā)生了。

解決方案是什么?那當然就是,發(fā)送完成后一定要記得關閉 channel。

nil channel

向 nil channel 發(fā)送和接收數(shù)據(jù)都將會導致阻塞。這種情況可能在我們定義 channel 時忘記初始化的時候發(fā)生。

示例代碼:

func main() {
 defer func() {
 time.Sleep(time.Second)
 fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
 }()
 var ch chan int
 go func() {
 -ch
 // ch-
 }()
}

兩種寫法:-ch 和 ch- 1,分別表示接收與發(fā)送,都將會導致阻塞。如果想實現(xiàn)阻塞,通過 nil channel 和 done channel 結合實現(xiàn)阻止 main 函數(shù)的退出,這或許是可以一試的方法。

func main() {
 defer func() {
 time.Sleep(time.Second)
 fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
 }()
 done := make(chan struct{})
 var ch chan int
 go func() {
 defer close(done)
 }()
 select {
 case -ch:
 case -done:
 return
 }
}

在 goroutine 執(zhí)行完成,檢測到 done 關閉,main 函數(shù)退出。

真實的場景

真實的場景肯定不會像案例中的簡單,可能涉及多階段 goroutine 之間的協(xié)作,某個 goroutine 可能即使接收者又是發(fā)送者。但歸根到底,無論什么使用模式。都是把基礎知識組織在一起的合理運用。

傳統(tǒng)同步機制

雖然,一般推薦 Go 并發(fā)數(shù)據(jù)的傳遞,但有些場景下,顯然還是使用傳統(tǒng)同步機制更合適。Go 中提供傳統(tǒng)同步機制主要在 sync 和 atomic 兩個包。接下來,我主要介紹的是鎖和 WaitGroup 可能導致 goroutine 的泄露。

Mutex

和其他語言類似,Go 中存在兩種鎖,排它鎖和共享鎖,關于它們的使用就不作介紹了。我們以排它鎖為例進行分析。

示例如下:

func main() {
 total := 0
 defer func() {
 time.Sleep(time.Second)
 fmt.Println("total: ", total)
 fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
 }()
 var mutex sync.Mutex
 for i := 0; i  2; i++ {
 go func() {
  mutex.Lock()
  total += 1
 }()
 }
}

執(zhí)行結果如下:

total: 1
the number of goroutines: 2

這段代碼通過啟動兩個 goroutine 對 total 進行加法操作,為防止出現(xiàn)數(shù)據(jù)競爭,對計算部分做了加鎖保護,但并沒有及時的解鎖,導致 i = 1 的 goroutine 一直阻塞等待 i = 0 的 goroutine 釋放鎖。可以看到,退出時有 2 個 goroutine 存在,出現(xiàn)了泄露,total 的值為 1。

怎么解決?因為 Go 有 defer 的存在,這個問題還是非常容易解決的,只要記得在 Lock 的時候,記住 defer Unlock 即可。

示例如下:

mutex.Lock()
defer mutext.Unlock()

其他的鎖與這里其實都是類似的。

WaitGroup

WaitGroup 和鎖有所差別,它類似 Linux 中的信號量,可以實現(xiàn)一組 goroutine 操作的等待。使用的時候,如果設置了錯誤的任務數(shù),也可能會導致阻塞,導致泄露發(fā)生。

一個例子,我們在開發(fā)一個后端接口時需要訪問多個數(shù)據(jù)表,由于數(shù)據(jù)間沒有依賴關系,我們可以并發(fā)訪問,示例如下:

package main
import (
 "fmt"
 "runtime"
 "sync"
 "time"
)
func handle() {
 var wg sync.WaitGroup
 wg.Add(4)
 go func() {
 fmt.Println("訪問表1")
 wg.Done()
 }()
 go func() {
 fmt.Println("訪問表2")
 wg.Done()
 }()
 go func() {
 fmt.Println("訪問表3")
 wg.Done()
 }()
 wg.Wait()
}
func main() {
 defer func() {
 time.Sleep(time.Second)
 fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
 }()
 go handle()
 time.Sleep(time.Second)
}

執(zhí)行結果如下:

the number of goroutines: 2

出現(xiàn)了泄露。再看代碼,它的開始部分定義了類型為 sync.WaitGroup 的變量 wg,設置并發(fā)任務數(shù)為 4,但是從例子中可以看出只有 3 個并發(fā)任務。故最后的 wg.Wait() 等待退出條件將永遠無法滿足,handle 將會一直阻塞。

怎么防止這類情況發(fā)生?

我個人的建議是,盡量不要一次設置全部任務數(shù),即使數(shù)量非常明確的情況。因為在開始多個并發(fā)任務之間或許也可能出現(xiàn)被阻斷的情況發(fā)生。最好是盡量在任務啟動時通過 wg.Add(1) 的方式增加。

示例如下:

 ...
 wg.Add(1)
 go func() {
 fmt.Println("訪問表1")
 wg.Done()
 }()
 wg.Add(1)
 go func() {
 fmt.Println("訪問表2")
 wg.Done()
 }()
 wg.Add(1)
 go func() {
 fmt.Println("訪問表3")
 wg.Done()
 }()
 ...

總結

大概介紹完了我認為的所有可能導致 goroutine 泄露的情況。總結下來,其實無論是死循環(huán)、channel 阻塞、鎖等待,只要是會造成阻塞的寫法都可能產(chǎn)生泄露。因而,如何防止 goroutine 泄露就變成了如何防止發(fā)生阻塞。為進一步防止泄露,有些實現(xiàn)中會加入超時處理,主動釋放處理時間太長的 goroutine。

以上所述是小編給大家介紹的Go 防止 goroutine 泄露的方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!

您可能感興趣的文章:
  • Go語言死鎖與goroutine泄露問題的解決

標簽:德宏 吐魯番 貴州 常州 東營 曲靖 許昌 保定

巨人網(wǎng)絡通訊聲明:本文標題《Go 防止 goroutine 泄露的方法》,本文關鍵詞  防止,goroutine,泄露,的,方法,;如發(fā)現(xiàn)本文內(nèi)容存在版權問題,煩請?zhí)峁┫嚓P信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《Go 防止 goroutine 泄露的方法》相關的同類信息!
  • 本頁收集關于Go 防止 goroutine 泄露的方法的相關信息資訊供網(wǎng)民參考!
  • 推薦文章
    婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av
    国产成人鲁色资源国产91色综| 日韩欧美一二三| 国产精品88av| 国产一区三区三区| 国内国产精品久久| 国产精一品亚洲二区在线视频| 国产一区二区不卡老阿姨| 国产精一品亚洲二区在线视频| 韩国精品久久久| 粉嫩高潮美女一区二区三区| 粉嫩一区二区三区性色av| 成人高清伦理免费影院在线观看| 成人高清av在线| 欧美专区在线观看一区| 欧美日韩一级片在线观看| 69av一区二区三区| 久久综合狠狠综合| 久久久av毛片精品| 最新不卡av在线| 亚洲小说春色综合另类电影| 一区二区三区丝袜| 男男gaygay亚洲| 国产专区欧美精品| 国产v日产∨综合v精品视频| 不卡的电影网站| 欧洲精品视频在线观看| 3atv在线一区二区三区| 精品成人免费观看| 国产精品国产三级国产| 亚洲高清三级视频| 青青草国产精品97视觉盛宴| 成人精品鲁一区一区二区| 91色婷婷久久久久合中文| 91精品蜜臀在线一区尤物| 日本一区二区三区高清不卡| 伊人婷婷欧美激情| 精品制服美女丁香| 91久久免费观看| 日韩欧美中文一区二区| 中文字幕日本不卡| 热久久国产精品| 国产精品99久久久久久有的能看 | 三级在线观看一区二区| 国产精品一区2区| 色偷偷88欧美精品久久久| 666欧美在线视频| 亚洲国产精品二十页| 日日夜夜精品免费视频| 成人爱爱电影网址| 26uuuu精品一区二区| 亚洲香肠在线观看| 不卡一区二区在线| 久久久久久久免费视频了| 天天影视色香欲综合网老头| 一本一本大道香蕉久在线精品| 2024国产精品| 蜜乳av一区二区| 欧美视频第二页| 亚洲美女视频在线观看| 国产福利一区在线| 2023国产精华国产精品| 免费久久精品视频| 欧美乱妇15p| 亚洲一区在线电影| 免费观看一级欧美片| 色婷婷av一区二区三区之一色屋| 91麻豆精品久久久久蜜臀| 曰韩精品一区二区| 91久久香蕉国产日韩欧美9色| 亚洲欧美自拍偷拍| 91色视频在线| 欧美一级欧美三级在线观看| 午夜日韩在线电影| 欧美影院一区二区三区| 亚洲自拍偷拍麻豆| 欧美日韩久久不卡| 亚洲国产裸拍裸体视频在线观看乱了| 91丨porny丨国产| 亚洲欧美日韩综合aⅴ视频| 91论坛在线播放| 亚洲一区二区精品久久av| 色天天综合久久久久综合片| 亚洲激情六月丁香| 欧美精选一区二区| 久久国产精品无码网站| 欧美亚州韩日在线看免费版国语版| 亚洲美女在线国产| 欧美午夜电影网| 日韩精品电影一区亚洲| 日韩欧美你懂的| 国产精品66部| 国产精品美日韩| 欧亚一区二区三区| 日本不卡视频一二三区| 久久男人中文字幕资源站| 成人午夜视频免费看| 亚洲黄色av一区| 日韩一区二区在线观看视频 | 欧美激情中文不卡| 久草热8精品视频在线观看| 国产日韩欧美精品在线| 91一区二区在线| 免费成人深夜小野草| 久久精品一区二区三区不卡牛牛 | 日韩一级二级三级精品视频| 国产在线精品免费| 一区二区三区欧美在线观看| 欧美一区二区三区成人| 国产成人自拍网| 午夜精品影院在线观看| 久久精品在这里| 欧美日韩一级视频| 成人晚上爱看视频| 亚洲国产中文字幕| 国产欧美一区二区三区鸳鸯浴| 欧美影院一区二区| 国产成人精品亚洲777人妖| 亚洲午夜私人影院| 亚洲国产高清不卡| 6080国产精品一区二区| av亚洲精华国产精华精华| 日本在线播放一区二区三区| 国产精品乱码妇女bbbb| 91精品国产入口在线| 91麻豆蜜桃一区二区三区| 狠狠色狠狠色综合系列| 亚洲午夜精品在线| 综合色天天鬼久久鬼色| 精品对白一区国产伦| 欧美亚洲动漫制服丝袜| 成人性色生活片| 国产成人a级片| 久久疯狂做爰流白浆xx| 亚洲影视在线播放| 亚洲天堂网中文字| 国产日韩欧美a| 久久只精品国产| 日韩欧美中文字幕一区| 欧美日韩国产一级片| 色哟哟国产精品| 成人福利视频网站| 国产1区2区3区精品美女| 精品一区二区三区在线观看国产| 视频在线观看一区| 日韩精品1区2区3区| 亚洲乱码国产乱码精品精可以看 | 欧美在线高清视频| 一本大道久久a久久精二百 | 一区二区三区在线视频观看| 国产欧美精品一区二区三区四区| 日韩免费电影网站| 26uuu亚洲综合色| 精品成人免费观看| 国产日韩欧美激情| 中文字幕在线一区| 国产精品国产精品国产专区不蜜 | 日本成人中文字幕在线视频| 香蕉影视欧美成人| 青青草伊人久久| 视频一区二区三区在线| 一级做a爱片久久| 亚洲福利电影网| 三级在线观看一区二区 | 国产欧美一区二区精品性| 久久这里只精品最新地址| 中文一区二区完整视频在线观看| 国产精品五月天| 亚洲免费在线播放| 亚洲成a人v欧美综合天堂| 日韩国产欧美在线视频| 极品尤物av久久免费看| 国产成人精品三级麻豆| 在线观看一区二区视频| 日韩一区二区在线免费观看| 久久夜色精品国产噜噜av| 国产偷v国产偷v亚洲高清| 一区二区三区在线视频播放| 免费久久精品视频| 国产成人午夜片在线观看高清观看| 国产91丝袜在线18| 欧美日韩国产免费一区二区 | 成人黄色网址在线观看| 一本大道av伊人久久综合| 欧美精品一二三| 久久亚洲一级片| 中文字幕不卡在线观看| 午夜精品视频在线观看| 韩国精品主播一区二区在线观看| 色综合久久66| 久久久久久久久久久久久久久99| 国产精品毛片久久久久久| 国产精品看片你懂得| 麻豆一区二区在线| 色综合欧美在线视频区| 精品国产麻豆免费人成网站| 亚洲精品亚洲人成人网在线播放| 另类成人小视频在线| 91久久精品一区二区三| 国产欧美日韩麻豆91| 蜜臀久久久99精品久久久久久|