以下是我學習中所接觸的關于Goroutine記憶體泄漏的例子
首先記憶體泄漏的情況會有如下幾種:
- Goroutine 內正在進行 channel/mutex 等讀寫操作,但由于邏輯問題,某些情況下會被一直阻塞,
- Goroutine 內的業務邏輯進入死回圈,資源一直無法釋放,
- Goroutine 內的業務邏輯進入長時間等待,有不斷新增的 Goroutine 進入等待,
demo1:
func main() {
for i := 0; i < 4; i++ {
queryAll()
fmt.Printf("goroutines: %d\n", runtime.NumGoroutine())
}
}
func queryAll() int {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func() { ch <- query() }()
} //開啟了三個協程,有兩個協程堵塞
return <-ch
}
func query() int {
n := rand.Intn(100)
time.Sleep(time.Duration(n) * time.Millisecond)
return n
}
上面的輸出結果是:
goroutines: 3
goroutines: 5
goroutines: 7
goroutines: 10
可看到輸出的 goroutines 數量是在不斷增加的,每次多 2 個,也就是每呼叫一次queryAll(),都會泄露 Goroutine,原因在于 無緩沖channel 均已經發送了(每次發送 3 個),但是在接收端并沒有完全接收(只接收 1 次 ch),所誘發的 Goroutine 泄露,并且main函式本身也算是一個goroutine
demo2:
func main() {
defer func() {
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
var ch chan struct{} //必須make
go func() {
ch <- struct{}{} //因為非緩沖?必須得有協程接收它才能存放?
}()
//<-ch
time.Sleep(time.Second)
}
輸出結果很明顯是:goroutines: 2
拋開管道chan沒有事先make在進行使用不說,對于非緩沖管道,必須有接收者才能將資料放入管道,否則就會阻塞!從而導致記憶體泄漏,
demo3:
func main() {
defer func() {
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
var ch chan int
go func() {
<-ch
}()
time.Sleep(time.Second)
}
與demo2相同道理,無緩沖管道不能只有一方在操作,否則就會堵塞
demo4:
//奇怪的慢等待
func main() {
httpClient := http.Client{
//Timeout: time.Second * 2, //設定httpClicent的超時時間,否則由于回應慢而一直堵塞
}
for {
go func() {
resp, err := httpClient.Get("https://www.github.com/")
if err != nil {
fmt.Printf("http.Get err: %v\n", err)
} else {
fmt.Printf(resp.Status)
}
}()
time.Sleep(time.Second * 1)
fmt.Println("goroutines: ", runtime.NumGoroutine())
}
}
輸出結果:(由于在我這經常訪問不了github,所以用這個做測驗~)
goroutines: 5
200 OK
200 OK
goroutines: 3
goroutines: 4
在上面demo中,是一個 Go 語言中經典的事故場景,也就是一般我們會在應用程式中去呼叫第三方服務的介面,但是第三方介面,有時候會很慢,久久不回傳回應結果,剛好在Go 語言中默認的 http.Client 是沒有設定超時時間的,因此就會導致一直阻塞,Goroutine數量 自然也就持續暴漲,不斷泄露,最終占滿資源,導致事故,
若我們設定了超時時間,若回應很慢也能結束掉這個goroutine,不至于導致記憶體泄漏!
demo5:
//互斥鎖忘記解鎖
func main() {
total := 0
defer func() {
time.Sleep(time.Second)
fmt.Println("total: ", total)
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
var mutex sync.Mutex
for i := 0; i < 10; i++ {
go func() {
mutex.Lock()
total += 1
}()
}
}
上面的代碼輸出結果如下:
total: 1
goroutines: 10
在上面這個demo中,第一個互斥鎖 sync.Mutex 加鎖了,由于他可能在處理業務邏輯,或者是忘記 Unlock 解鎖了,因此導致后面的所有 sync.Mutex 想加鎖,卻因鎖未釋放又都阻塞住了, 建議在加鎖后來一句defer mutex.Unlock()
last demo:
//同步鎖使用不當
func handle(v int) {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < v; i++ {
fmt.Println("巴拉巴拉巴拉...")
wg.Done()
}
wg.Wait()
}
func main() {
defer func() {
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
go handle(3)
time.Sleep(time.Second)
}
輸出結果:
巴拉巴拉巴拉…
巴拉巴拉巴拉…
巴拉巴拉巴拉…
goroutines: 2
由于使用了同步鎖進行編排!但是由于wg.Add的數量與wg.Done數量不匹配,導致wg.Wait一直在等待而導致阻塞!所以可以輸出了3個巴拉是傳進去引數的結果,但是該goroutine并沒有結束,一直在等待還差兩個wg.Done沒有執行,所以程式結束前有兩個goroutine,所以怎么處理這種情況呢,建議在for回圈內執行wg.Add(1),defer wg.Done()以及dosomething…
以上就是今天的六個小demo!要堅持每天學習喔!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/280636.html
標籤:其他
