亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺析Go語言中內(nèi)存泄漏的原因與解決方法

 更新時(shí)間:2024年02月06日 10:44:34   作者:banjming  
這篇文章主要來和大家聊一聊Go語言中內(nèi)存泄漏的那些事,例如內(nèi)存泄漏的原因與解決方法,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考下

遵循一個(gè)約定:如果goroutine負(fù)責(zé)創(chuàng)建goroutine,它也負(fù)責(zé)確保他可以停止 goroutine

channel 泄漏

發(fā)送不接收,一般來說發(fā)送者,正常發(fā)送,接收者正常接收,這樣沒啥問題。但是一旦接收者異常,發(fā)送者會(huì)被阻塞,造成泄漏。

select case 導(dǎo)致協(xié)程泄漏

func leakOfMemory() {
	errChan := make(chan error) //a.
	go func() {
		time.Sleep(2 * time.Second)
		errChan <- errors.New("chan error") // b.
		fmt.Println("finish ending ")
	}()

	select {
	case <-time.After(time.Second): 
		fmt.Println("超時(shí)") //c
	case err := <-errChan: //d.
		fmt.Println("err:", err)
	}
	fmt.Println("leakOfMemory exit")
}

func TestLeakOfMemory(t *testing.T) {
	leakOfMemory()
	time.Sleep(3 * time.Second)
	fmt.Println("main exit...")
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
}

上面的代碼執(zhí)行結(jié)果:

=== RUN   TestLeakOfMemory
超時(shí)
leakOfMemory exit
main exit...
NumGoroutine: 3
--- PASS: TestLeakOfMemory (4.00s)
PASS

最開始只有兩個(gè) goruntine ,為啥執(zhí)行后有三個(gè) goruntine ?

由于沒有往 errChan 中發(fā)送消息,所以 d 處 會(huì)一直阻塞,1s 后 ,c 處打印超時(shí),程序退出,此時(shí),有個(gè)協(xié)程在 b 處往協(xié)程中塞值,但是此時(shí)外面的 goruntine 已經(jīng)退出了,此時(shí) errChan 沒有接收者,那么就會(huì)在 b處阻塞,因此協(xié)程一直沒有退出,造成了泄漏,如果有很多類似的代碼,會(huì)造成 OOM。

for range 導(dǎo)致的協(xié)程泄漏

看如下代碼:

func leakOfMemory_1(nums ...int) {
	out := make(chan int)
	// sender
	go func() {
		defer close(out)
		for _, n := range nums { // c.
			out <- n
			time.Sleep(time.Second)
		}
	}()

	// receiver
	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		for n := range out { //b.
			if ctx.Err() != nil { //a.
				fmt.Println("ctx timeout ")
				return
			}
			fmt.Println(n)
		}
	}()

}

func TestLeakOfMemory(t *testing.T) {
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
	leakOfMemory_1(1, 2, 3, 4, 5, 6, 7)
	time.Sleep(3 * time.Second)
	fmt.Println("main exit...")
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
}

上述代碼執(zhí)行結(jié)果:

=== RUN   TestLeakOfMemory
NumGoroutine: 2
1
2
ctx timeout 
main exit...
NumGoroutine: 3
--- PASS: TestLeakOfMemory (3.00s)
PASS

理論上,是不是最開始只有2個(gè)goruntine ,實(shí)際上執(zhí)行完出現(xiàn)了3個(gè)gorountine, 說明 leakOfMemory_1 里面起碼有一個(gè)協(xié)程沒有退出。 因?yàn)闀r(shí)間到了,在 a 出,程序就準(zhǔn)備退出了,也就是說 b 這個(gè)就退出了,沒有接收者繼續(xù)接受 chan 中的數(shù)據(jù)了,c處往chan 寫數(shù)據(jù)就阻塞了,因此協(xié)程一直沒有退出,就造成了泄漏。

如何解決上面說的協(xié)程泄漏問題?

可以加個(gè)管道通知來防止內(nèi)存泄漏。

func leakOfMemory_2(done chan struct{}, nums ...int) {
	out := make(chan int)
	// sender
	go func() {
		defer close(out)
		for _, n := range nums {
			select {
			case out <- n:
			case <-done:
				return
			}
			time.Sleep(time.Second)
		}
	}()

	// receiver
	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		for n := range out {
			if ctx.Err() != nil {
				fmt.Println("ctx timeout ")
				return
			}
			fmt.Println(n)
		}
	}()
}
func TestLeakOfMemory(t *testing.T) {
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
	done := make(chan struct{})
	defer close(done)
	leakOfMemory_2(done, 1, 2, 3, 4, 5, 6, 7)
	time.Sleep(3 * time.Second)
	done <- struct{}{}
	fmt.Println("main exit...")
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
}

代碼執(zhí)行結(jié)果:

=== RUN   TestLeakOfMemory
NumGoroutine: 2
1
2
ctx timeout 
main exit...
NumGoroutine: 2
--- PASS: TestLeakOfMemory (3.00s)
PASS

最開始是 2個(gè) goruntine 程序結(jié)束后還2個(gè) goruntine,沒有協(xié)程泄漏。

goruntine 中 map 并發(fā)

map 是引用類型,函數(shù)值傳值是調(diào)用,參數(shù)副本依然指向m,因?yàn)橹祩鬟f的是引用,對(duì)于共享變量,資源并發(fā)讀寫會(huì)產(chǎn)生競(jìng)爭(zhēng),故共享資源遭受到破壞。

func TestConcurrencyMap(t *testing.T) {
	m := make(map[int]int)
	go func() {
		for {
			m[3] = 3
		}

	}()
	go func() {
		for {
			m[2] = 2
		}
	}()
	//select {}
	time.Sleep(10 * time.Second)
}

上訴代碼執(zhí)行結(jié)果:

=== RUN   TestConcurrencyMap
fatal error: concurrent map writes

goroutine 5 [running]:
runtime.throw({0x1121440?, 0x0?})
    /go/go1.18.8/src/runtime/panic.go:992 +0x71 fp=0xc000049f78 sp=0xc000049f48 pc=0x10333b1
...

用火焰圖分析下內(nèi)存泄漏問題

首先,程序代碼運(yùn)行前,需要加這個(gè)代碼:

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"
	_ "net/http/pprof"
	"runtime"
	"testing"
	"time"
)

func TestLeakOfMemory(t *testing.T) {

	//leakOfMemory()
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
	for i := 0; i < 1000; i++ {
		go leakOfMemory_1(1, 2, 3, 4, 5, 6, 7)
	}
	//done := make(chan struct{})
	//defer close(done)
	//leakOfMemory_2(done, 1, 2, 3, 4, 5, 6, 7)
	time.Sleep(3 * time.Second)
	//done <- struct{}{}
	fmt.Println("main exit...")
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
	log.Println(http.ListenAndServe("localhost:6060", nil))
}

上面的執(zhí)行后,登陸網(wǎng)址 http://localhost:6060/debug/pprof/goroutine?debug=1,可以看到下面的頁面:

但是看不到圖形界面,怎么辦?

需要安裝 graphviz

在控制臺(tái)執(zhí)行如下命令

brew install graphviz # 安裝graphviz,只需要安裝一次就行了
go tool pprof -http=":8081" http://localhost:6060/debug/pprof/goroutine?debug=1

然后可以登陸網(wǎng)頁:http://localhost:8081/ui/ 看到下圖:

發(fā)現(xiàn)有一個(gè)程序//GoProject/main/concurrency/channel.leakOfMemory_1.func1占用 cpu 特別大. 想看下這個(gè)程序是啥?

分析協(xié)程泄漏

使用如下結(jié)果:

go tool pprof http://localhost:6060/debug/pprof/goroutine

火焰圖分析:

Total:總共采樣次數(shù),100次。

Flat:函數(shù)在樣本中處于運(yùn)行狀態(tài)的次數(shù)。簡(jiǎn)單來說就是函數(shù)出現(xiàn)在棧頂?shù)拇螖?shù),而函數(shù)在棧頂則意味著它在使用CPU。

Flat%:Flat / Total。

Sum%:自己以及所有前面的Flat%的累積值。解讀方式:表中第3行Sum% 32.4%,意思是前3個(gè)函數(shù)(運(yùn)行狀態(tài))的計(jì)數(shù)占了總樣本數(shù)的32.4%

Cum:函數(shù)在樣本中出現(xiàn)的次數(shù)。只要這個(gè)函數(shù)出現(xiàn)在棧中那么就算進(jìn)去,這個(gè)和Flat不同(必須是棧頂才能算進(jìn)去)。也可以解讀為這個(gè)函數(shù)的調(diào)用次數(shù)。

Cum%:Cum / Total

進(jìn)入控制臺(tái),輸入 top

Type: goroutine
Time: Feb 5, 2024 at 10:02am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1003, 99.90% of 1004 total
Dropped 35 nodes (cum <= 5)
      flat  flat%   sum%        cum   cum%
      1003 99.90% 99.90%       1003 99.90%  runtime.gopark
         0     0% 99.90%       1000 99.60%  //GoProject/main/concurrency/channel.leakOfMemory_1.func1
         0     0% 99.90%       1000 99.60%  runtime.chansend
         0     0% 99.90%       1000 99.60%  runtime.chansend1
(pprof)

其中 其中runtime.gopark即可認(rèn)為是掛起的goroutine數(shù)量。發(fā)現(xiàn)有大量協(xié)程被 runtime.gopark

然后輸入 traces runtime.gopark

(pprof) traces  runtime.gopark
Type: goroutine
Time: Feb 5, 2024 at 10:02am (CST)
-----------+-------------------------------------------------------
      1000   runtime.gopark
             runtime.chansend
             runtime.chansend1
             //GoProject/main/concurrency/channel.leakOfMemory_1.func1
-----------+-------------------------------------------------------
         1   runtime.gopark
             runtime.chanrecv
             runtime.chanrecv1
             testing.(*T).Run
             testing.runTests.func1
             testing.tRunner
             testing.runTests
             testing.(*M).Run
             main.main
             runtime.main
-----------+-------------------------------------------------------
         1   runtime.gopark
             runtime.netpollblock
             internal/poll.runtime_pollWait
             internal/poll.(*pollDesc).wait
             internal/poll.(*pollDesc).waitRead (inline)
             internal/poll.(*FD).Read
             net.(*netFD).Read
             net.(*conn).Read
             net/http.(*connReader).backgroundRead
-----------+-------------------------------------------------------
         1   runtime.gopark
             runtime.netpollblock
             internal/poll.runtime_pollWait
             internal/poll.(*pollDesc).wait
             internal/poll.(*pollDesc).waitRead (inline)
             internal/poll.(*FD).Accept
             net.(*netFD).accept
             net.(*TCPListener).accept
             net.(*TCPListener).Accept
             net/http.(*Server).Serve
             net/http.(*Server).ListenAndServe
             net/http.ListenAndServe (inline)
             //GoProject/main/concurrency/channel.TestLeakOfMemory
             testing.tRunner
-----------+-------------------------------------------------------
(pprof)

可以發(fā)現(xiàn)泄漏了 1000 個(gè) goruntine。

然后通過調(diào)用棧,可以看到調(diào)用鏈路:

channel.leakOfMemory_1.func1->runtime.chansend1->runtime.chansend->runtime.gopark

runtime.chansend1 是阻塞的調(diào)用,協(xié)程最終被 runtime.gopark 掛起,從而導(dǎo)致泄漏。

然后再輸入 list GoProject/main/concurrency/channel. leakOfMemory_1.func1 可以看到如下

(pprof) list //GoProject/main/concurrency/channel.
leakOfMemory_1.func1
Total: 1004
ROUTINE ======================== //GoProject/main/concurrency/channel.leakOfMemory_1.func1 in /Users/bytedance/go/src///GoProject/main/concurrency/channel/channel_test.go
         0       1000 (flat, cum) 99.60% of Total
         .          .     62:    out := make(chan int)
         .          .     63:    // sender
         .          .     64:    go func() {
         .          .     65:        defer close(out)
         .          .     66:        for _, n := range nums {
         .       1000     67:            out <- n
         .          .     68:            time.Sleep(time.Second)
         .          .     69:        }
         .          .     70:    }()
         .          .     71:
         .          .     72:    // receiver

可以看到使用了一個(gè)非緩沖的 channel, 上面已經(jīng)分析了,沒有接收者,發(fā)送者out 在寫入channel 時(shí)阻塞, 協(xié)程無法退出,因此有協(xié)程泄漏。

分析內(nèi)存增長泄漏

go tool pprof http://localhost:6060/debug/pprof/heap

然后輸入 top

(pprof) top
Showing nodes accounting for 6662.08kB, 86.68% of 7686.14kB total
Showing top 10 nodes out of 24
      flat  flat%   sum%        cum   cum%
 5125.63kB 66.69% 66.69%  5125.63kB 66.69%  runtime.allocm
 1024.41kB 13.33% 80.01%  1024.41kB 13.33%  runtime.malg
  512.05kB  6.66% 86.68%   512.05kB  6.66%  internal/poll.runtime_Semacquire
         0     0% 86.68%   512.05kB  6.66%  GoProject/main/concurrency/channel.leakOfMemory_1.func2
         0     0% 86.68%   512.05kB  6.66%  fmt.Fprintln
         0     0% 86.68%   512.05kB  6.66%  fmt.Println (inline)
         0     0% 86.68%   512.05kB  6.66%  internal/poll.(*FD).Write
         0     0% 86.68%   512.05kB  6.66%  internal/poll.(*FD).writeLock (inline)
         0     0% 86.68%   512.05kB  6.66%  internal/poll.(*fdMutex).rwlock
         0     0% 86.68%   512.05kB  6.66%  os.(*File).Write
(pprof)

看著不是很大,達(dá)不到內(nèi)存增長泄漏的級(jí)別。

以上就是淺析Go語言中內(nèi)存泄漏的原因與解決方法的詳細(xì)內(nèi)容,更多關(guān)于Go內(nèi)存泄漏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go使用Redis實(shí)現(xiàn)分布式鎖的常見方法

    Go使用Redis實(shí)現(xiàn)分布式鎖的常見方法

    Redis?提供了一些原語,可以幫助我們實(shí)現(xiàn)高效的分布式鎖,下邊是使用?Redis?實(shí)現(xiàn)分布式鎖的一種常見方法,通過代碼示例給大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下
    2024-11-11
  • golang中tar壓縮和解壓文件詳情

    golang中tar壓縮和解壓文件詳情

    這篇文章主要給大家介紹golang中tar壓縮和解壓文件,文章以查看官方文檔自帶的給大家演習(xí)一下golang的archive/tar壓縮和解壓功能,需要的朋友可以參考一下
    2021-11-11
  • Go網(wǎng)絡(luò)編程TCP抓包實(shí)操示例探究

    Go網(wǎng)絡(luò)編程TCP抓包實(shí)操示例探究

    作為一名軟件開發(fā)者,網(wǎng)絡(luò)編程是必備知識(shí),本文通過?Go?語言實(shí)現(xiàn)?TCP?套接字編程,并結(jié)合?tcpdump?工具,展示它的三次握手、數(shù)據(jù)傳輸以及四次揮手的過程,幫助讀者更好地理解?TCP?協(xié)議與?Go?網(wǎng)絡(luò)編程
    2024-01-01
  • GO中使用谷歌GEMINI模型任務(wù)代碼實(shí)例

    GO中使用谷歌GEMINI模型任務(wù)代碼實(shí)例

    這篇文章主要為大家介紹了GO中使用谷歌GEMINI模型任務(wù)代碼實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • go grpc高級(jí)用法

    go grpc高級(jí)用法

    RPC是遠(yuǎn)程過程調(diào)用,可以像調(diào)用本地服務(wù)一樣取調(diào)用遠(yuǎn)程服務(wù),本文主要介紹了go grpc高級(jí)用法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Go uuid庫的具體使用

    Go uuid庫的具體使用

    在現(xiàn)代軟件開發(fā)中,全球唯一標(biāo)識(shí)符(UUID)在許多場(chǎng)景中發(fā)揮著重要的作用,本文主要介紹了Go uuid庫的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11
  • golang panic 函數(shù)用法示例詳解

    golang panic 函數(shù)用法示例詳解

    在Go語言中,panic用于觸發(fā)不可恢復(fù)的錯(cuò)誤,終止函數(shù)執(zhí)行并逐層向上觸發(fā)defer,最終若未被recover捕獲,程序會(huì)崩潰,recover用于在defer函數(shù)中捕獲panic,恢復(fù)程序流程,建議優(yōu)先返回error,僅在嚴(yán)重錯(cuò)誤或不可恢復(fù)場(chǎng)景下使用panic,并在關(guān)鍵位置recover,感興趣的朋友一起看看吧
    2025-03-03
  • golang-gorm自動(dòng)建表問題

    golang-gorm自動(dòng)建表問題

    這篇文章主要介紹了golang-gorm自動(dòng)建表問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Golang如何將上傳的文件壓縮成zip(小案例)

    Golang如何將上傳的文件壓縮成zip(小案例)

    這篇文章主要介紹了Golang如何將上傳的文件壓縮成zip(小案例),這是一個(gè)簡(jiǎn)單的golang壓縮文件小案例,可做很多的拓展,這里使用的庫是archive/zip,在gopkg里面搜zip就行,需要的朋友可以參考下
    2024-01-01
  • 分享6個(gè)Go處理字符串的技巧小結(jié)

    分享6個(gè)Go處理字符串的技巧小結(jié)

    這篇文章主要介紹了分享6個(gè)Go處理字符串的技巧小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12

最新評(píng)論