最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang內(nèi)存泄漏場(chǎng)景以及解決方案詳析

 更新時(shí)間:2023年01月10日 10:02:12   作者:8023之永恒  
golang中內(nèi)存泄露的發(fā)現(xiàn)與排查一直是來(lái)是go開發(fā)者頭疼的一件事,下面這篇文章主要給大家介紹了關(guān)于Golang內(nèi)存泄漏場(chǎng)景以及解決的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下

1、字符串截取

func main() {
	var str0 = "12345678901234567890"
	str1 := str0[:10]
}

以上代碼,會(huì)有10字節(jié)的內(nèi)存泄漏,我們知道,str0和str1底層共享內(nèi)存,只要str1一直活躍,str0 就不會(huì)被回收,10字節(jié)的內(nèi)存被使用,剩下的10字節(jié)內(nèi)存就造成了臨時(shí)性的內(nèi)存泄漏,直到str1不再活躍

如果str0足夠大,str1截取足夠小,或者在高并發(fā)場(chǎng)景中頻繁使用,那么可想而知,會(huì)造成臨時(shí)性內(nèi)存泄漏,對(duì)性能產(chǎn)生極大影響。

解決方案1:string to []byte, []byte to string

func main() {
	var str0 = "12345678901234567890"
	str1 := string([]byte(str0[:10]))
}

將需要截取的部分先轉(zhuǎn)換成[]byte,再轉(zhuǎn)換成string,但是這種方式會(huì)產(chǎn)生兩個(gè)10字節(jié)的臨時(shí)變量,string轉(zhuǎn)換[]byte時(shí)產(chǎn)生一個(gè)10字節(jié)臨時(shí)變量,[]byte轉(zhuǎn)換string時(shí)產(chǎn)生一個(gè)10字節(jié)的臨時(shí)變量

解決方案2

func main() {
	var str0 = "12345678901234567890"
	str1 := (" " + str0[:10])[1:]
}

這種方式仍舊會(huì)產(chǎn)生1字節(jié)的浪費(fèi) 

解決方案3:strings.Builder

func main() {
	var str0 = "12345678901234567890"
	var builder strings.Builder
	builder.Grow(10)
	builder.WriteString(str0[:10])
	str1 := builder.String()
}

這種方式的缺點(diǎn)就是代碼量過多

解決方案4:strings.Repeat

func main() {
	var str0 = "12345678901234567890"
	str1 := strings.Repeat(str0[:10], 1)
}

這種方式底層還是用到了strings.Builder,優(yōu)點(diǎn)就是將方案3進(jìn)行了封裝,代碼量得到了精簡(jiǎn)

2、切片截取引起子切片內(nèi)存泄漏

func main() {
	var s0 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := s0[:5]
}

這種情況與字符串截取引起的內(nèi)存泄漏情況類似,s1活躍情況下,造成s0中部分內(nèi)存泄漏

解決方案:append

func main() {
	var s0 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := append(s0[:0:0], s0[:5]...)
}

 append為內(nèi)置函數(shù),go源碼src/builtin/builtin.go中釋義:

// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
//	slice = append(slice, elem1, elem2)
//	slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
//	slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type

3、沒有重置丟失的子切片元素中的指針

func main() {
	var s0 = []*int{new(int), new(int), new(int), new(int), new(int)}
	s1 := s0[1:3]
}

原切片元素為指針類型,原切片被截取后,丟失的子切片元素中的指針元素未被置空,導(dǎo)致內(nèi)存泄漏

解決方案:元素置空 

func main() {
	var s0 = []*int{new(int), new(int), new(int), new(int), new(int)}
	s0[0], s0[3], s0[4] = nil, nil, nil
	s1 := s0[1:3]
}

4、函數(shù)數(shù)組傳參

Go數(shù)組是值類型,賦值和函數(shù)傳參都會(huì)復(fù)制整個(gè)數(shù)組

func main() {
	var arrayA = [3]int{1, 2, 3}
	var arrayB = [3]int{}
	arrayB = arrayA
	fmt.Printf("arrayA address: %p, arrayA value: %+v\n", &arrayA, arrayA)
	fmt.Printf("arrayB address: %p, arrayB value: %+v\n", &arrayB, arrayB)
	array(arrayA)
}
 
func array(array [3]int) {
	fmt.Printf("array address: %p, array value: %+v\n", &array, array)
}

 打印結(jié)果:

arrayA address: 0xc0000ae588, arrayA value: [1 2 3]
arrayB address: 0xc0000ae5a0, arrayB value: [1 2 3]
array address: 0xc0000ae5e8, array value: [1 2 3]

可以看到,三條打印的地址都不相同,說明數(shù)組是值傳遞的,這會(huì)導(dǎo)致什么問題呢?

如果我們?cè)诤瘮?shù)傳參的時(shí)候用到了數(shù)組傳參,且這個(gè)數(shù)組夠大(我們假設(shè)數(shù)組大小為100萬(wàn),64位機(jī)上消耗的內(nèi)存約為800w字節(jié),即8MB內(nèi)存),或者該函數(shù)短時(shí)間內(nèi)被調(diào)用N次,那么可想而知,會(huì)消耗大量?jī)?nèi)存,對(duì)性能產(chǎn)生極大的影響,如果短時(shí)間內(nèi)分配大量?jī)?nèi)存,而又來(lái)不及GC,那么就會(huì)產(chǎn)生臨時(shí)性的內(nèi)存泄漏,對(duì)于高并發(fā)場(chǎng)景相當(dāng)可怕。

解決方案1:采用指針傳遞

func main() {
	var arrayA = [3]int{1, 2, 3}
	var arrayB = &arrayA
	fmt.Printf("arrayA address: %p, arrayA value: %+v\n", &arrayA, arrayA)
	fmt.Printf("arrayB address: %p, arrayB value: %+v\n", arrayB, *arrayB)
	arrayP(&arrayA)
}
 
func arrayP(array *[3]int) {
	fmt.Printf("array address: %p, array value: %+v\n", array, *array)
}

打印結(jié)果: 

arrayA address: 0xc00000e6a8, arrayA value: [1 2 3]
arrayB address: 0xc00000e6a8, arrayB value: [1 2 3]
array address: 0xc00000e6a8, array value: [1 2 3]

可以看到,三條打印的地址相同,說明指針是引用傳遞的 ,三個(gè)數(shù)組指向的都是同一塊內(nèi)存,就算數(shù)組很大,或者函數(shù)短時(shí)間被調(diào)用N次,也不會(huì)產(chǎn)生額外的內(nèi)存開銷,這樣會(huì)不會(huì)有隱患呢?

有,如果arrayA的指針地址發(fā)生變化,那么,arrayB和函數(shù)內(nèi)array的指針地址也隨之改變,稍不注意,容易發(fā)生bug

解決方案2:利用切片可以很好的解決以上兩個(gè)問題

func main() {
	var arrayA = [3]int{1, 2, 3}
	var arrayB = arrayA[:]
	fmt.Printf("arrayA address: %p, arrayA value: %+v\n", &arrayA, arrayA)
	fmt.Printf("arrayB address: %p, arrayB value: %+v\n", &arrayB, arrayB)
	arrayS(arrayB)
}
 
func arrayS(array []int) {
	fmt.Printf("array address: %p, array value: %+v\n", &array, array)
}

打印結(jié)果:

arrayA address: 0xc00000e6a8, arrayA value: [1 2 3]
arrayB address: 0xc0000040d8, arrayB value: [1 2 3]
array address: 0xc000004108, array value: [1 2 3]

 可以看到,三條打印的地址都不相同,而切片本身是一個(gè)引用類型,arrayA和arrayB底層共享內(nèi)存,不會(huì)產(chǎn)生額外內(nèi)存開銷,而且arrayA的指針地址發(fā)生改變,arrayB的指針地址也不會(huì)改變,切片的數(shù)據(jù)結(jié)構(gòu)如下:

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

5、goroutine

“Go里面10次內(nèi)存泄漏有9次都是goroutine泄漏引起的”

有些編碼不當(dāng)?shù)那闆r下,goroutine被長(zhǎng)期掛住,導(dǎo)致該協(xié)程中的內(nèi)存也無(wú)法被釋放,就會(huì)造成永久性的內(nèi)存泄漏。例如協(xié)程結(jié)束時(shí)協(xié)程中的channel沒有關(guān)閉,導(dǎo)致一直阻塞;例如協(xié)程中有死循環(huán);等等

我們來(lái)看下

func main() {
	ticker := time.NewTicker(time.Second * 1)
	for {
		<-ticker.C
		ch := make(chan int)
		go func() {
			for i := 0; i < 100; i++ {
				ch <- i
			}
		}()
 
		for v := range ch {
			if v == 50 {
				break
			}
		}
	}
}

將代碼運(yùn)行起來(lái),并利用pprof工具,在web輸入http://localhost/debug/pprof/,我們可以看到,goroutine的數(shù)量隨著時(shí)間在不斷的增加,而且絲毫沒有減少的跡象

 這是因?yàn)閎reak的時(shí)候,協(xié)程中的channel并沒有關(guān)閉,導(dǎo)致協(xié)程一直存活,無(wú)法被回收

解決方案:

func main() {
	ticker := time.NewTicker(time.Second * 1)
	for {
		<-ticker.C
		cxt, cancel := context.WithCancel(context.Background())
		ch := make(chan int)
		go func(cxt context.Context) {
			for i := 0; i < 100; i++ {
				select {
				case <-cxt.Done():
					return
				case ch <- i:
				}
			}
		}(cxt)
 
		for v := range ch {
			if v == 50 {
				cancel()
				break
			}
		}
	}
}

利用context,在break之前cancel,目的就是通知協(xié)程退出,這樣就避免了goroutine泄漏 

6、定時(shí)器

1)time.After

func main() {
	ch := make(chan int)
	go func() {
		for {
			timerC := time.After(100 * time.Second)
			//timerC 每次都是重新創(chuàng)建的,什么意思呢?簡(jiǎn)單說來(lái),當(dāng) select 成功監(jiān)聽 ch 并進(jìn)入它的處理分支,下次循環(huán) timerC 重新創(chuàng)建了,時(shí)間肯定就重置了。
			select {
			//如果有多個(gè) case 都可以運(yùn)行,select 會(huì)隨機(jī)公平選擇出一個(gè)執(zhí)行。其余的則不會(huì)執(zhí)行
			case num := <-ch:
				fmt.Println("get num is", num)
			case <-timerC:
				//等價(jià)于 case <-time.After(100 * time.Second)
				fmt.Println("time's up!!!")
				//done<-true
			}
		}
	}()
 
	for i := 1; i < 100000; i++ {
		ch <- i
		time.Sleep(time.Millisecond)
	}
}

 以上代碼會(huì)造成內(nèi)存泄漏,time.After底層實(shí)現(xiàn)是一個(gè)timer,而定時(shí)器未到觸發(fā)時(shí)間,該定時(shí)器不會(huì)被gc回收,從而導(dǎo)致臨時(shí)性的內(nèi)存泄漏,而如果定時(shí)器一直在創(chuàng)建,那么就造成了永久性的內(nèi)存泄漏了。

解決方案:采用timer定時(shí)器

func main() {
	ch := make(chan int)
	go func() {
		timer := time.NewTimer(100 * time.Second)
		defer timer.Stop()
		for {
			timer.Reset(100 * time.Second)
			select {
			case num := <-ch:
				fmt.Println("get num is", num)
			case <-timer.C:
				fmt.Println("time's up!!!")
			}
		}
	}()
 
	for i := 1; i < 100000; i++ {
		ch <- i
		time.Sleep(time.Millisecond)
	}
}

 創(chuàng)建timer定時(shí)器,每次需要啟動(dòng)定時(shí)器的時(shí)候,使用Reset方法重置定時(shí)器,這樣就不用每次都要?jiǎng)?chuàng)建新的定時(shí)器了

2)timer、ticker

在高并發(fā)、高性能場(chǎng)景中,使用time.NewTimer或者time.NewTicker定時(shí)器,都需要注意及時(shí)調(diào)用Stop方法來(lái)及時(shí)釋放資源,否則可能造成臨時(shí)性或者永久性的內(nèi)存泄漏。

總結(jié)

到此這篇關(guān)于Golang內(nèi)存泄漏場(chǎng)景以及解決方案的文章就介紹到這了,更多相關(guān)Golang內(nèi)存泄漏場(chǎng)景及解決內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go返回int64類型字段超出javascript Number范圍的解決方法

    Go返回int64類型字段超出javascript Number范圍的解決方法

    這篇文章主要介紹了Go返回int64類型字段超出javascript Number范圍的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • golang sudog指的是什么

    golang sudog指的是什么

    sudog代表在等待隊(duì)列中的goroutine,比如channel發(fā)送接受,由于goroutine和同步對(duì)象的關(guān)系是多對(duì)多,因此需要sudog映射,本文重點(diǎn)介紹golang sudog指的是什么,感興趣的朋友一起看看吧
    2024-02-02
  • Go中并發(fā)控制的實(shí)現(xiàn)方式總結(jié)

    Go中并發(fā)控制的實(shí)現(xiàn)方式總結(jié)

    在Go實(shí)際開發(fā)中,并發(fā)安全是老生常談的事情,在并發(fā)下,goroutine之間的存在數(shù)據(jù)資源等方面的競(jìng)爭(zhēng),為了保證數(shù)據(jù)一致性、防止死鎖等問題的出現(xiàn),在并發(fā)中需要使用一些方式來(lái)實(shí)現(xiàn)并發(fā)控制,本文給大家總結(jié)了幾種實(shí)現(xiàn)方式,需要的朋友可以參考下
    2023-12-12
  • 詳解Golang中Channel的高級(jí)用法

    詳解Golang中Channel的高級(jí)用法

    在Go語(yǔ)言中,chan(通道)是一種用于在不同的goroutine之間進(jìn)行通信的機(jī)制,通道可以是無(wú)緩沖的(同步的)或有緩沖的(異步的),本文給大家詳細(xì)介紹了Golang中Channel的高級(jí)用法,需要的朋友可以參考下
    2024-05-05
  • Go每日一庫(kù)之quicktemplate的使用

    Go每日一庫(kù)之quicktemplate的使用

    quicktemplate快速、功能強(qiáng)大、易于使用的Go模板引擎。比html/模板快20倍,本文我們就詳細(xì)的介紹一下quicktemplate的具體使用,感興趣的可以了解一下
    2021-07-07
  • GoLang讀取文件的10種方法實(shí)例

    GoLang讀取文件的10種方法實(shí)例

    讀取文件是所有編程語(yǔ)言中最常見的操作之一,下面這篇文章主要給大家介紹了關(guān)于GoLang讀取文件的10種方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • Go語(yǔ)言在Linux環(huán)境下輸出彩色字符的方法

    Go語(yǔ)言在Linux環(huán)境下輸出彩色字符的方法

    這篇文章主要介紹了Go語(yǔ)言在Linux環(huán)境下輸出彩色字符的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-04-04
  • Go語(yǔ)言基礎(chǔ)語(yǔ)法之結(jié)構(gòu)體及方法詳解

    Go語(yǔ)言基礎(chǔ)語(yǔ)法之結(jié)構(gòu)體及方法詳解

    結(jié)構(gòu)體類型可以用來(lái)保存不同類型的數(shù)據(jù),也可以通過方法的形式來(lái)聲明它的行為。本文將介紹go語(yǔ)言中的結(jié)構(gòu)體和方法,以及“繼承”的實(shí)現(xiàn)方法
    2021-09-09
  • golang游戲等資源壓縮包創(chuàng)建和操作方法

    golang游戲等資源壓縮包創(chuàng)建和操作方法

    這篇文章主要介紹了golang游戲等資源壓縮包創(chuàng)建和操作,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • 使用go來(lái)操作redis的方法示例

    使用go來(lái)操作redis的方法示例

    今天小編就為大家分享一篇關(guān)于使用go來(lái)操作redis的方法示例,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-04-04

最新評(píng)論

图木舒克市| 新蔡县| 和龙市| 紫金县| 昭平县| 通榆县| 旬阳县| 定安县| 融水| 佳木斯市| 绩溪县| 卢湾区| 灯塔市| 青龙| 朝阳区| 灵寿县| 秦皇岛市| 衡水市| 遵义市| 岑溪市| 雷州市| 巢湖市| 兴业县| 随州市| 郁南县| 漳浦县| 云林县| 巴南区| 松滋市| 河东区| 阳江市| 金华市| 林州市| 漾濞| 宿松县| 西吉县| 洪江市| 介休市| 文昌市| 金塔县| 武平县|