目錄
golang容易導致記憶體洩漏的幾種情況
1.1 time.After()的使用
#1.2 time.NewTicker資源未及時釋放" > #1.2 time.NewTicker資源未及時釋放
2. select阻塞" > 2. select阻塞
2.1 導致goroutine阻塞的情況" >2.1 導致goroutine阻塞的情況
2.2 迴圈空轉導致CPU暴漲" >2.2 迴圈空轉導致CPU暴漲
#3. channel阻塞" >#3. channel阻塞
#4. goroutine導致的記憶體洩漏
4.1 申請過多的goroutine
#4.2 goroutine阻塞" >#4.2 goroutine阻塞
4.2. 1 I/O問題
4.2.2 互斥鎖未釋放" >4.2.2 互斥鎖未釋放
#4.2.3 死鎖" >#4.2.3 死鎖
4.2. 4 waitgroup使用不當" >4.2. 4 waitgroup使用不當
##5. slice 引起的內存洩漏" >##5. slice 引起的內存洩漏
6. 陣列的值傳遞" >6. 陣列的值傳遞
首頁 後端開發 Golang golang記憶體洩漏原因有哪些

golang記憶體洩漏原因有哪些

Jan 10, 2023 pm 05:45 PM
golang go語言 內存洩漏

洩漏原因有:1、time.After()的使用,每次time.After(duration x)會產生NewTimer(),在duration x到期前,新建立的timer不會被GC ,到期後才會GC;2、time.NewTicker資源未及時釋放;3、select阻塞;4、channel阻塞;5、申請過多的goroutine、goroutine阻塞;6、slice引起的等。

golang記憶體洩漏原因有哪些

本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。

golang容易導致記憶體洩漏的幾種情況

#1. 定時器使用不當

1.1 time.After()的使用

預設的time.After()是會有記憶體洩漏問題的,因為每次time.After(duration x)會產生NewTimer( ),在duration x到期之前,新創建的timer不會被GC,到期之後才會GC。

隨著時間推移,尤其是duration x很大的話,會產生記憶體外洩的問題,應特別注意

for true {
	select {
	case <-time.After(time.Minute * 3):
    // do something
  default:
		time.Sleep(time.Duration(1) * time.Second)
	}
}
登入後複製

為了保險起見,使用NewTimer()或NewTicker()代替的方式主動釋放資源,兩者的差異請自行查閱或看我往期文章https://blog.csdn.net/weixin_38299404/article/details/119352884

timer := time.NewTicker(time.Duration(2) * time.Second)
defer timer.Stop()
for true {
	select {
	case <-timer.C:
		// do something
	default:
		time.Sleep(time.Duration(1) * time.Second)
	}
}
登入後複製

#1.2 time.NewTicker資源未及時釋放

在使用time.NewTicker時需要手動呼叫Stop()方法釋放資源,否則將會造成永久性的記憶體洩漏

timer := time.NewTicker(time.Duration(2) * time.Second)
// defer timer.Stop()
for true {
	select {
	case <-timer.C:
		// do something
	default:
		time.Sleep(time.Duration(1) * time.Second)
	}
}
登入後複製

2. select阻塞

使用select時如果有case沒有覆蓋完全的情況且沒有default分支進行處理,最終會導致記憶體洩漏

2.1 導致goroutine阻塞的情況

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    ch3 := make(chan int)
    go Getdata("https://www.baidu.com",ch1)
    go Getdata("https://www.baidu.com",ch2)
    go Getdata("https://www.baidu.com",ch3)
    select{
        case v:=<- ch1:
            fmt.Println(v)
        case v:=<- ch2:
            fmt.Println(v)
    }
}
登入後複製

上述這種情況會阻塞在ch3的消費處導致記憶體洩漏

2.2 迴圈空轉導致CPU暴漲

func main() {
	fmt.Println("main start")
	msgList := make(chan int, 100)
	go func() {
		for {
			select {
			case <-msgList:
			default:
 
			}
		}
	}()
	
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	s := <-c
	
	fmt.Println("main exit.get signal:", s)
}
登入後複製

上述for迴圈條件一旦命中default則會出現循環空轉的情況,並最終導致CPU暴漲

#3. channel阻塞

channel阻塞主要分為寫入阻塞和讀取阻塞兩種情況

空channel

func channelTest() {
  	//声明未初始化的channel读写都会阻塞
    var c chan int
  	//向channel中写数据
    go func() {
        c <- 1
        fmt.Println("g1 send succeed")
        time.Sleep(1 * time.Second)
    }()
  	//从channel中读数据
    go func() {
        <-c
        fmt.Println("g2 receive succeed")
        time.Sleep(1 * time.Second)
    }()
    time.Sleep(10 * time.Second)
}
登入後複製

寫阻塞

  • 無緩衝channel的阻塞通常是寫操作因為沒有讀取而阻塞
func channelTest() {
    var c = make(chan int)
  	//10个协程向channel中写数据
    for i := 0; i < 10; i++ {
        go func() {
            <- c
            fmt.Println("g1 receive succeed")
            time.Sleep(1 * time.Second)
        }()
    }
  	//1个协程丛channel读数据
    go func() {
        c <- 1
        fmt.Println("g2 send succeed")
        time.Sleep(1 * time.Second)
    }()
  	//会有写的9个协程阻塞得不到释放
    time.Sleep(10 * time.Second)
}
登入後複製

 

  • 有緩衝的channel因為緩衝區滿了,寫入操作阻塞
func channelTest() {
    var c = make(chan int, 8)
  	//10个协程向channel中写数据
    for i := 0; i < 10; i++ {
        go func() {
            <- c
            fmt.Println("g1 receive succeed")
            time.Sleep(1 * time.Second)
        }()
    }
  	//1个协程丛channel读数据
    go func() {
        c <- 1
        fmt.Println("g2 send succeed")
        time.Sleep(1 * time.Second)
    }()
  	//会有写的几个协程阻塞写不进去
    time.Sleep(10 * time.Second)
}
登入後複製

讀取阻塞

  • 期待從channel讀數據,結果沒有goroutine往進寫資料
func channelTest() {
   var c = make(chan int)
  //1个协程向channel中写数据
  go func() {
    <- c
    fmt.Println("g1 receive succeed")
    time.Sleep(1 * time.Second)
  }()
  //10个协程丛channel读数据
  for i := 0; i < 10; i++ {
    go func() {
        c <- 1
        fmt.Println("g2 send succeed")
        time.Sleep(1 * time.Second)
    }()
  }
  //会有读的9个协程阻塞得不到释放
  time.Sleep(10 * time.Second)
}
登入後複製

#4. goroutine導致的記憶體洩漏

4.1 申請過多的goroutine

#例如在for循環中申請過多的goroutine來不及釋放導致記憶體洩漏

#4.2 goroutine阻塞

4.2. 1 I/O問題

I/O連線未設定逾時時間,導致goroutine一直在等待,程式碼會一直阻塞。

4.2.2 互斥鎖未釋放

goroutine無法取得到鎖定資源,導致goroutine阻塞

//协程拿到锁未释放,其他协程获取锁会阻塞
func mutexTest() {
    mutex := sync.Mutex{}
    for i := 0; i < 10; i++ {
        go func() {
            mutex.Lock()
            fmt.Printf("%d goroutine get mutex", i)
      			//模拟实际开发中的操作耗时
            time.Sleep(100 * time.Millisecond)
        }()
    }
    time.Sleep(10 * time.Second)
}
登入後複製
#4.2.3 死鎖

當程式死鎖時其他goroutine也會阻塞

func mutexTest() {
    m1, m2 := sync.Mutex{}, sync.RWMutex{}
  	//g1得到锁1去获取锁2
    go func() {
        m1.Lock()
        fmt.Println("g1 get m1")
        time.Sleep(1 * time.Second)
        m2.Lock()
        fmt.Println("g1 get m2")
    }()
    //g2得到锁2去获取锁1
    go func() {
        m2.Lock()
        fmt.Println("g2 get m2")
        time.Sleep(1 * time.Second)
        m1.Lock()
        fmt.Println("g2 get m1")
    }()
  	//其余协程获取锁都会失败
    go func() {
        m1.Lock()
        fmt.Println("g3 get m1")
    }()
    time.Sleep(10 * time.Second)
}
登入後複製
4.2. 4 waitgroup使用不當

waitgroup的Add、Done和wait數量不匹配會導致wait一直在等待

##5. slice 引起的內存洩漏

當兩個slice 共享位址,其中一個為全域變量,另一個也無法被GC;

append slice 後一直使用,沒有進行清理。

var a []int
 
func test(b []int) {
        a = b[:3]
        return
}
登入後複製

6. 陣列的值傳遞

#由於陣列時Golang的基本資料類型,每個陣列佔用不通的記憶體空間,生命週期互不干擾,很難出現記憶體洩漏的情況,但是數組作為形參傳輸時,遵循的時值拷貝,如果函數被多個goroutine調用且數組過大時,則會導致記憶體使用激增。

//统计nums中target出现的次数
func countTarget(nums [1000000]int, target int) int {
    num := 0
    for i := 0; i < len(nums) && nums[i] == target; i++ {
        num++
    }
    return num
}
登入後複製
因此對於大數組放在形參場景下通常使用切片或指標進行傳遞,避免短時間的記憶體使用激增、

【相關推薦:

Go影片教學程式設計教學#

以上是golang記憶體洩漏原因有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Go的爬蟲Colly中Queue線程的問題是什麼? Go的爬蟲Colly中Queue線程的問題是什麼? Apr 02, 2025 pm 02:09 PM

Go爬蟲Colly中的Queue線程問題探討在使用Go語言的Colly爬蟲庫時,開發者常常會遇到關於線程和請求隊列的問題。 �...

Go語言中用於浮點數運算的庫有哪些? Go語言中用於浮點數運算的庫有哪些? Apr 02, 2025 pm 02:06 PM

Go語言中用於浮點數運算的庫介紹在Go語言(也稱為Golang)中,進行浮點數的加減乘除運算時,如何確保精度是�...

在 Go 語言中,為什麼使用 Println 和 string() 函數打印字符串會出現不同的效果? 在 Go 語言中,為什麼使用 Println 和 string() 函數打印字符串會出現不同的效果? Apr 02, 2025 pm 02:03 PM

Go語言中字符串打印的區別:使用Println與string()函數的效果差異在Go...

在Go語言中使用Redis Stream實現消息隊列時,如何解決user_id類型轉換問題? 在Go語言中使用Redis Stream實現消息隊列時,如何解決user_id類型轉換問題? Apr 02, 2025 pm 04:54 PM

Go語言中使用RedisStream實現消息隊列時類型轉換問題在使用Go語言與Redis...

GoLand中自定義結構體標籤不顯示怎麼辦? GoLand中自定義結構體標籤不顯示怎麼辦? Apr 02, 2025 pm 05:09 PM

GoLand中自定義結構體標籤不顯示怎麼辦?在使用GoLand進行Go語言開發時,很多開發者會遇到自定義結構體標籤在�...

Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Apr 02, 2025 pm 04:12 PM

Go語言中哪些庫是大公司開發或知名開源項目?在使用Go語言進行編程時,開發者常常會遇到一些常見的需求,�...

Golang的目的:建立高效且可擴展的系統 Golang的目的:建立高效且可擴展的系統 Apr 09, 2025 pm 05:17 PM

Go語言在構建高效且可擴展的系統中表現出色,其優勢包括:1.高性能:編譯成機器碼,運行速度快;2.並發編程:通過goroutines和channels簡化多任務處理;3.簡潔性:語法簡潔,降低學習和維護成本;4.跨平台:支持跨平台編譯,方便部署。

在Go編程中,如何正確管理Mysql和Redis的連接與釋放資源? 在Go編程中,如何正確管理Mysql和Redis的連接與釋放資源? Apr 02, 2025 pm 05:03 PM

Go編程中的資源管理:Mysql和Redis的連接與釋放在學習Go編程過程中,如何正確管理資源,特別是與數據庫和緩存�...

See all articles