您好,登錄后才能下訂單哦!
這篇“golang內(nèi)存泄漏的原因是什么”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“golang內(nèi)存泄漏的原因是什么”文章吧。
泄漏原因有:1、time.After()的使用,每次time.After(duration x)會產(chǎn)生NewTimer(),在duration x到期之前,新創(chuàng)建的timer不會被GC,到期之后才會GC;2、time.NewTicker資源未及時釋放;3、select阻塞;4、channel阻塞;5、申請過多的goroutine、goroutine阻塞;6、slice引起的等。
默認的time.After()是會有內(nèi)存泄露問題的,因為每次time.After(duration x)會產(chǎn)生NewTimer(),在duration x到期之前,新創(chuàng)建的timer不會被GC,到期之后才會GC。
隨著時間推移,尤其是duration x很大的話,會產(chǎn)生內(nèi)存泄露的問題,應(yīng)特別注意
for true {
select {
case <-time.After(time.Minute * 3):
// do something
default:
time.Sleep(time.Duration(1) * time.Second)
}
}
為了保險起見,使用NewTimer()或者NewTicker()代替的方式主動釋放資源
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)
}
}
在使用time.NewTicker時需要手動調(diào)用Stop()方法釋放資源,否則將會造成永久性的內(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)
}
}
使用select時如果有case沒有覆蓋完全的情況且沒有default分支進行處理,最終會導(dǎo)致內(nèi)存泄漏
func main() {
ch2 := make(chan int)
ch3 := make(chan int)
ch4 := make(chan int)
go Getdata("https://www.baidu.com",ch2)
go Getdata("https://www.baidu.com",ch3)
go Getdata("https://www.baidu.com",ch4)
select{
case v:=<- ch2:
fmt.Println(v)
case v:=<- ch3:
fmt.Println(v)
}
}
上述這種情況會阻塞在ch4的消費處導(dǎo)致內(nèi)存泄漏
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則會出現(xiàn)循環(huán)空轉(zhuǎn)的情況,并最終導(dǎo)致CPU暴漲
channel阻塞主要分為寫阻塞和讀阻塞兩種情況
空channel
func channelTest() {
//聲明未初始化的channel讀寫都會阻塞
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)
}
寫阻塞
無緩沖channel的阻塞通常是寫操作因為沒有讀而阻塞
func channelTest() {
var c = make(chan int)
//10個協(xié)程向channel中寫數(shù)據(jù)
for i := 0; i < 10; i++ {
go func() {
<- c
fmt.Println("g1 receive succeed")
time.Sleep(1 * time.Second)
}()
}
//1個協(xié)程叢channel讀數(shù)據(jù)
go func() {
c <- 1
fmt.Println("g2 send succeed")
time.Sleep(1 * time.Second)
}()
//會有寫的9個協(xié)程阻塞得不到釋放
time.Sleep(10 * time.Second)
}
有緩沖的channel因為緩沖區(qū)滿了,寫操作阻塞
func channelTest() {
var c = make(chan int, 8)
//10個協(xié)程向channel中寫數(shù)據(jù)
for i := 0; i < 10; i++ {
go func() {
<- c
fmt.Println("g1 receive succeed")
time.Sleep(1 * time.Second)
}()
}
//1個協(xié)程叢channel讀數(shù)據(jù)
go func() {
c <- 1
fmt.Println("g2 send succeed")
time.Sleep(1 * time.Second)
}()
//會有寫的幾個協(xié)程阻塞寫不進去
time.Sleep(10 * time.Second)
}
讀阻塞
期待從channel讀數(shù)據(jù),結(jié)果沒有g(shù)oroutine往進寫數(shù)據(jù)
func channelTest() {
var c = make(chan int)
//1個協(xié)程向channel中寫數(shù)據(jù)
go func() {
<- c
fmt.Println("g1 receive succeed")
time.Sleep(1 * time.Second)
}()
//10個協(xié)程叢channel讀數(shù)據(jù)
for i := 0; i < 10; i++ {
go func() {
c <- 1
fmt.Println("g2 send succeed")
time.Sleep(1 * time.Second)
}()
}
//會有讀的9個協(xié)程阻塞得不到釋放
time.Sleep(10 * time.Second)
}
例如在for循環(huán)中申請過多的goroutine來不及釋放導(dǎo)致內(nèi)存泄漏
I/O連接未設(shè)置超時時間,導(dǎo)致goroutine一直在等待,代碼會一直阻塞。
goroutine無法獲取到鎖資源,導(dǎo)致goroutine阻塞
//協(xié)程拿到鎖未釋放,其他協(xié)程獲取鎖會阻塞
func mutexTest() {
mutex := sync.Mutex{}
for i := 0; i < 10; i++ {
go func() {
mutex.Lock()
fmt.Printf("%d goroutine get mutex", i)
//模擬實際開發(fā)中的操作耗時
time.Sleep(100 * time.Millisecond)
}()
}
time.Sleep(10 * time.Second)
}
當(dāng)程序死鎖時其他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")
}()
//其余協(xié)程獲取鎖都會失敗
go func() {
m1.Lock()
fmt.Println("g3 get m1")
}()
time.Sleep(10 * time.Second)
}
waitgroup的Add、Done和wait數(shù)量不匹配會導(dǎo)致wait一直在等待
當(dāng)兩個slice 共享地址,其中一個為全局變量,另一個也無法被GC;
append slice 后一直使用,沒有進行清理。
var a []int
func test(b []int) {
a = b[:3]
return
}
由于數(shù)組時Golang的基本數(shù)據(jù)類型,每個數(shù)組占用不通的內(nèi)存空間,生命周期互不干擾,很難出現(xiàn)內(nèi)存泄漏的情況,但是數(shù)組作為形參傳輸時,遵循的時值拷貝,如果函數(shù)被多個goroutine調(diào)用且數(shù)組過大時,則會導(dǎo)致內(nèi)存使用激增。
//統(tǒng)計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
}
因此對于大數(shù)組放在形參場景下通常使用切片或者指針進行傳遞,避免短時間的內(nèi)存使用激增。
以上就是關(guān)于“golang內(nèi)存泄漏的原因是什么”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。