golang容易導(dǎo)致內(nèi)存泄漏的6種情況匯總
1. 定時(shí)器使用不當(dāng)
1.1 time.After()的使用
默認(rèn)的time.After()是會(huì)有內(nèi)存泄露問(wèn)題的,因?yàn)槊看蝨ime.After(duration x)會(huì)產(chǎn)生NewTimer(),在duration x到期之前,新創(chuàng)建的timer不會(huì)被GC,到期之后才會(huì)GC。
隨著時(shí)間推移,尤其是duration x很大的話,會(huì)產(chǎn)生內(nèi)存泄露的問(wèn)題,應(yīng)特別注意
for true {
select {
case <-time.After(time.Minute * 3):
// do something
default:
time.Sleep(time.Duration(1) * time.Second)
}
}
為了保險(xiǎn)起見(jiàn),使用NewTimer()或者NewTicker()代替的方式主動(dòng)釋放資源,兩者的區(qū)別請(qǐng)自行查閱或看我往期文章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資源未及時(shí)釋放
在使用time.NewTicker時(shí)需要手動(dòng)調(diào)用Stop()方法釋放資源,否則將會(huì)造成永久性的內(nèi)存泄漏
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時(shí)如果有case沒(méi)有覆蓋完全的情況且沒(méi)有default分支進(jìn)行處理,最終會(huì)導(dǎo)致內(nèi)存泄漏
2.1 導(dǎo)致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)
}
}
上述這種情況會(huì)阻塞在ch3的消費(fèi)處導(dǎo)致內(nèi)存泄漏
2.2 循環(huán)空轉(zhuǎn)導(dǎo)致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循環(huán)條件一旦命中default則會(huì)出現(xiàn)循環(huán)空轉(zhuǎn)的情況,并最終導(dǎo)致CPU暴漲
3. channel阻塞
channel阻塞主要分為寫阻塞和讀阻塞兩種情況
空channel
func channelTest() {
//聲明未初始化的channel讀寫都會(huì)阻塞
var c chan int
//向channel中寫數(shù)據(jù)
go func() {
c <- 1
fmt.Println("g1 send succeed")
time.Sleep(1 * time.Second)
}()
//從channel中讀數(shù)據(jù)
go func() {
<-c
fmt.Println("g2 receive succeed")
time.Sleep(1 * time.Second)
}()
time.Sleep(10 * time.Second)
}
寫阻塞
無(wú)緩沖channel的阻塞通常是寫操作因?yàn)闆](méi)有讀而阻塞
func channelTest() {
var c = make(chan int)
//10個(gè)協(xié)程向channel中寫數(shù)據(jù)
for i := 0; i < 10; i++ {
go func() {
<- c
fmt.Println("g1 receive succeed")
time.Sleep(1 * time.Second)
}()
}
//1個(gè)協(xié)程叢channel讀數(shù)據(jù)
go func() {
c <- 1
fmt.Println("g2 send succeed")
time.Sleep(1 * time.Second)
}()
//會(huì)有寫的9個(gè)協(xié)程阻塞得不到釋放
time.Sleep(10 * time.Second)
}
有緩沖的channel因?yàn)榫彌_區(qū)滿了,寫操作阻塞
func channelTest() {
var c = make(chan int, 8)
//10個(gè)協(xié)程向channel中寫數(shù)據(jù)
for i := 0; i < 10; i++ {
go func() {
<- c
fmt.Println("g1 receive succeed")
time.Sleep(1 * time.Second)
}()
}
//1個(gè)協(xié)程叢channel讀數(shù)據(jù)
go func() {
c <- 1
fmt.Println("g2 send succeed")
time.Sleep(1 * time.Second)
}()
//會(huì)有寫的幾個(gè)協(xié)程阻塞寫不進(jìn)去
time.Sleep(10 * time.Second)
}
讀阻塞
期待從channel讀數(shù)據(jù),結(jié)果沒(méi)有g(shù)oroutine往進(jìn)寫數(shù)據(jù)
func channelTest() {
var c = make(chan int)
//1個(gè)協(xié)程向channel中寫數(shù)據(jù)
go func() {
<- c
fmt.Println("g1 receive succeed")
time.Sleep(1 * time.Second)
}()
//10個(gè)協(xié)程叢channel讀數(shù)據(jù)
for i := 0; i < 10; i++ {
go func() {
c <- 1
fmt.Println("g2 send succeed")
time.Sleep(1 * time.Second)
}()
}
//會(huì)有讀的9個(gè)協(xié)程阻塞得不到釋放
time.Sleep(10 * time.Second)
}
4. goroutine導(dǎo)致的內(nèi)存泄漏
4.1 申請(qǐng)過(guò)多的goroutine
例如在for循環(huán)中申請(qǐng)過(guò)多的goroutine來(lái)不及釋放導(dǎo)致內(nèi)存泄漏
4.2 goroutine阻塞
4.2.1 I/O問(wèn)題
I/O連接未設(shè)置超時(shí)時(shí)間,導(dǎo)致goroutine一直在等待,代碼會(huì)一直阻塞。
4.2.2 互斥鎖未釋放
goroutine無(wú)法獲取到鎖資源,導(dǎo)致goroutine阻塞
//協(xié)程拿到鎖未釋放,其他協(xié)程獲取鎖會(huì)阻塞
func mutexTest() {
mutex := sync.Mutex{}
for i := 0; i < 10; i++ {
go func() {
mutex.Lock()
fmt.Printf("%d goroutine get mutex", i)
//模擬實(shí)際開(kāi)發(fā)中的操作耗時(shí)
time.Sleep(100 * time.Millisecond)
}()
}
time.Sleep(10 * time.Second)
}
4.2.3 死鎖
當(dāng)程序死鎖時(shí)其他goroutine也會(huì)阻塞
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")
}()
//其余協(xié)程獲取鎖都會(huì)失敗
go func() {
m1.Lock()
fmt.Println("g3 get m1")
}()
time.Sleep(10 * time.Second)
}
4.2.4 waitgroup使用不當(dāng)
waitgroup的Add、Done和wait數(shù)量不匹配會(huì)導(dǎo)致wait一直在等待
5. slice 引起的內(nèi)存泄漏
當(dāng)兩個(gè)slice 共享地址,其中一個(gè)為全局變量,另一個(gè)也無(wú)法被GC;
append slice 后一直使用,沒(méi)有進(jìn)行清理。
var a []int
func test(b []int) {
a = b[:3]
return
}
6. 數(shù)組的值傳遞
由于數(shù)組時(shí)Golang的基本數(shù)據(jù)類型,每個(gè)數(shù)組占用不通的內(nèi)存空間,生命周期互不干擾,很難出現(xiàn)內(nèi)存泄漏的情況,但是數(shù)組作為形參傳輸時(shí),遵循的時(shí)值拷貝,如果函數(shù)被多個(gè)goroutine調(diào)用且數(shù)組過(guò)大時(shí),則會(huì)導(dǎo)致內(nèi)存使用激增。
//統(tǒng)計(jì)nums中target出現(xiàn)的次數(shù)
func countTarget(nums [1000000]int, target int) int {
num := 0
for i := 0; i < len(nums) && nums[i] == target; i++ {
num++
}
return num
}
因此對(duì)于大數(shù)組放在形參場(chǎng)景下通常使用切片或者指針進(jìn)行傳遞,避免短時(shí)間的內(nèi)存使用激增。
總結(jié)
到此這篇關(guān)于golang容易導(dǎo)致內(nèi)存泄漏的6種情況的文章就介紹到這了,更多相關(guān)golang內(nèi)存泄漏內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Golang開(kāi)發(fā)一個(gè)簡(jiǎn)易版shell
這篇文章主要為大家詳細(xì)介紹了如何使用Golang開(kāi)發(fā)一個(gè)簡(jiǎn)易版shell,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02
Go語(yǔ)言字符串及strings和strconv包使用實(shí)例
字符串是工作中最常用的,值得我們專門的練習(xí)一下,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言字符串及strings和strconv包使用的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-06-06
解決golang時(shí)間字符串轉(zhuǎn)time.Time的坑
這篇文章主要介紹了解決golang時(shí)間字符串轉(zhuǎn)time.Time的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
Go語(yǔ)言常見(jiàn)錯(cuò)誤之a(chǎn)ny沒(méi)傳遞任何信息解決分析
Go語(yǔ)言,由于其高效強(qiáng)大的并行處理能力和優(yōu)雅簡(jiǎn)單的設(shè)計(jì)哲學(xué),一直以來(lái)都是編程世界的寵兒,然而,對(duì)于一些Go新手和甚至熟悉Go的程序員也可能會(huì)遇到一個(gè)常見(jiàn)的錯(cuò)誤:?any沒(méi)傳遞任何信息,那么,如何規(guī)避這個(gè)錯(cuò)誤,本文將揭示其中的秘密2024-01-01
golang 微服務(wù)之gRPC與Protobuf的使用
這篇文章主要介紹了golang 微服務(wù)之gRPC與Protobuf的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
go語(yǔ)言入門環(huán)境搭建及GoLand安裝教程詳解
這篇文章主要介紹了go語(yǔ)言入門環(huán)境搭建及GoLand安裝教程詳解,需要的朋友可以參考下2020-12-12
一文教你打造一個(gè)簡(jiǎn)易的Golang日志庫(kù)
這篇文章主要為大家詳細(xì)介紹了如何使用不超過(guò)130行的代碼,通過(guò)一系列g(shù)olang的特性,來(lái)打造一個(gè)簡(jiǎn)易的golang日志庫(kù),感興趣的小伙伴可以了解一下2023-06-06
GO語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單TCP服務(wù)的方法
這篇文章主要介紹了GO語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單TCP服務(wù)的方法,實(shí)例分析了Go語(yǔ)言實(shí)現(xiàn)TCP服務(wù)的技巧,需要的朋友可以參考下2015-03-03
Golang如何實(shí)現(xiàn)任意進(jìn)制轉(zhuǎn)換的方法示例
進(jìn)制轉(zhuǎn)換是人們利用符號(hào)來(lái)計(jì)數(shù)的方法,進(jìn)制轉(zhuǎn)換由一組數(shù)碼符號(hào)和兩個(gè)基本因素“基數(shù)”與“位權(quán)”構(gòu)成,這篇文章主要給大家介紹了關(guān)于Golang如何實(shí)現(xiàn)10進(jìn)制轉(zhuǎn)換62進(jìn)制的方法,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí)學(xué)習(xí),下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09

