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

Golang因Channel未關(guān)閉導(dǎo)致內(nèi)存泄漏的解決方案詳解

 更新時(shí)間:2023年07月24日 09:27:31   作者:Paualf  
這篇文章主要為大家詳細(xì)介紹了當(dāng)Golang因Channel未關(guān)閉導(dǎo)致內(nèi)存泄漏時(shí)蓋如何解決,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下

現(xiàn)象

某一個(gè)周末我們的服務(wù) oom了,一個(gè)比較重要的job 沒(méi)有跑完,需要重跑,以為是偶然,重跑成功,因?yàn)槭侵苣](méi)有去定位原因
又一個(gè)工作日,它又oom了,重跑成功,持續(xù)觀察,job 在oom之前竟然占用了30g左右(這里我們的任務(wù)比較大的數(shù)據(jù)量都在內(nèi)存中計(jì)算,所以這里機(jī)器內(nèi)存量大一點(diǎn))

應(yīng)用使用30g內(nèi)存肯定是不正常的,懷疑內(nèi)存泄漏了,怎么定位內(nèi)存泄漏呢?

定位

搜了一下網(wǎng)上經(jīng)常用到的工具是 go 的 pprof 火焰圖,自己在本地跑了一下,因?yàn)閿?shù)據(jù)量比較少,并沒(méi)有發(fā)現(xiàn)什么,暫時(shí)放下了。
后續(xù)某個(gè)早上在公司工具里面打開(kāi)了一下,發(fā)現(xiàn)有火焰圖的工具,打開(kāi)看了一下一個(gè)函數(shù)占用了 7224.46mb,占用了 7個(gè)g, 而且這個(gè)函數(shù)是已經(jīng)跑完了,這個(gè)時(shí)候定位到那個(gè)函數(shù)了,和旁邊同事說(shuō)了一下,同事幫忙看了下郵件告警,每個(gè)下午都會(huì)有任務(wù)失敗告警(任務(wù)失敗會(huì)進(jìn)行重試的); 這里懷疑是失敗了, channel 沒(méi)有關(guān)閉,導(dǎo)致 消費(fèi)的go routine 沒(méi)有回收。

舉個(gè)例子看下代碼:

package main
import (
	"context"
	"fmt"
	"golang.org/x/sync/errgroup"
)
func main() {
	readGroup, _ := errgroup.WithContext(context.Background())
	consumeGroup, _ := errgroup.WithContext(context.Background())
	var (
		data = make(chan []int, 10)
	)
	//  3個(gè)生產(chǎn)者往里面進(jìn)行進(jìn)行生產(chǎn)
	readGroup.Go(func() error {
		for i := 0; i < 3; i++ {
			data <- []int{i}
		}
		return nil
	})
	readGroup.Go(func() error {
		for i := 3; i < 6; i++ {
			data <- []int{i}
		}
		return nil
	})
	readGroup.Go(func() (err error) {
		for i := 6; i < 9; i++ {
			// error
			if i == 7 {
				err = fmt.Errorf("error le")
				return
			}
			data <- []int{i}
		}
		return nil
	})
	// 其中一個(gè)生產(chǎn)者遇到error 返回導(dǎo)致 channel 沒(méi)有關(guān)閉,消費(fèi)者沒(méi)有退出
	// 1個(gè)消費(fèi)者進(jìn)行消費(fèi)
	consumeGroup.Go(func() error {
		for i := range data {
			fmt.Println(i)
		}
		return nil
	})
	if err := readGroup.Wait(); err != nil {
		fmt.Println(err)
		return
	}
	close(data)
	if err := consumeGroup.Wait(); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("end it")
}

這個(gè)case里面,readGroup 遇到error 直接退出了,channel并沒(méi)有關(guān)閉,如果是常駐進(jìn)程的程序,消費(fèi)的go routine 并沒(méi)有回收,就導(dǎo)致了內(nèi)存泄漏

最簡(jiǎn)單的關(guān)閉修復(fù)

將 close 放到最上面的 defer close(data)

不過(guò)最好的還是生產(chǎn)者進(jìn)行關(guān)閉,我們可以?xún)?yōu)化一下代碼,把生產(chǎn)者的代碼放到一個(gè)函數(shù)中,這樣就可以讓生產(chǎn)者去進(jìn)行關(guān)閉的操作了

package main
import (
	"context"
	"fmt"
	"golang.org/x/sync/errgroup"
)
func main() {
	var (
		data = make(chan []int, 10)
		err  error
		eg, _ = errgroup.WithContext(context.Background())
	)
	eg.Go(func() (err error) {
		defer close(data)
		err = readGroup(data)
		return
	})
	eg.Go(func() (err error) {
		err = consumeGroup(data)
		return
	})
	err = eg.Wait()
	if err != nil {
		return
	}
	fmt.Println("end it")
}
func consumeGroup(data chan []int) (err error) {
	consumeGroup, _ := errgroup.WithContext(context.Background())
	consumeGroup.Go(func() error {
		for i := range data {
			fmt.Println(i)
		}
		return nil
	})
	if err = consumeGroup.Wait(); err != nil {
		fmt.Println(err)
		return
	}
	return
}
func readGroup(data chan []int) (err error) {
	readGroup, _ := errgroup.WithContext(context.Background())
	//  3個(gè)生產(chǎn)者往里面進(jìn)行進(jìn)行生產(chǎn)
	readGroup.Go(func() error {
		for i := 0; i < 3; i++ {
			data <- []int{i}
		}
		return nil
	})
	readGroup.Go(func() error {
		for i := 3; i < 6; i++ {
			data <- []int{i}
		}
		return nil
	})
	readGroup.Go(func() (err error) {
		for i := 6; i < 9; i++ {
			// error
			if i == 7 {
				err = fmt.Errorf("error le")
				return
			}
			data <- []int{i}
		}
		return nil
	})
	if err = readGroup.Wait(); err != nil {
		fmt.Println(err)
		return
	}
	return
}

修復(fù)

將生產(chǎn)者放在一個(gè) go routine 里面,最后如果遇到error的話(huà) defer()的時(shí)候會(huì)把channel給關(guān)閉了

The Channel Closing Principle
One general principle of using Go channels is don't close a channel from the receiver side and don't close a channel if the channel has multiple concurrent senders. In other words, we should only close a channel in a sender goroutine if the sender is the only sender of the channel.

簡(jiǎn)單點(diǎn):就是在生產(chǎn)者中進(jìn)行channel的關(guān)閉

后續(xù)討論和遇到的新問(wèn)題

拆分代碼函數(shù)的時(shí)候又遇到新的問(wèn)題了,有一個(gè)切片數(shù)組我拆分函數(shù)的時(shí)候,我沒(méi)有去接受切片函數(shù)的返回值,導(dǎo)致了切片發(fā)生擴(kuò)容返回的是一個(gè)空切片,并沒(méi)有修改掉原來(lái)的切片。之前以為在golang里面切片是引用類(lèi)型,會(huì)自動(dòng)改變其中的值最后查了一下,在go 里面都是值傳遞,可以修改其中的值其實(shí)是使用了指針修改了同一塊地址中的值所以值發(fā)生了變化

總結(jié)

使用channel 的時(shí)候在生產(chǎn)者中進(jìn)行關(guān)閉,思考一些遇到error的時(shí)候channel是否可以正常的關(guān)閉

go 中只有值傳遞,引用傳遞是修改了同一個(gè)指向內(nèi)存地址中的值

參考文章

Golang優(yōu)雅關(guān)閉channel的方法示例

Go語(yǔ)言參數(shù)傳遞是傳值還是傳引用

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

相關(guān)文章

  • golang使用grpc+go-kit模擬oauth認(rèn)證的操作

    golang使用grpc+go-kit模擬oauth認(rèn)證的操作

    這篇文章主要介紹了golang使用grpc+go-kit模擬oauth認(rèn)證的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • Go語(yǔ)言使用protojson庫(kù)實(shí)現(xiàn)Protocol Buffers與JSON轉(zhuǎn)換

    Go語(yǔ)言使用protojson庫(kù)實(shí)現(xiàn)Protocol Buffers與JSON轉(zhuǎn)換

    本文主要介紹Google開(kāi)源的工具庫(kù)Protojson庫(kù)如何Protocol Buffers與JSON進(jìn)行轉(zhuǎn)換,以及和標(biāo)準(zhǔn)庫(kù)encoding/json的性能對(duì)比,需要的朋友可以參考下
    2023-09-09
  • Golang實(shí)現(xiàn)gRPC的Proxy的原理解析

    Golang實(shí)現(xiàn)gRPC的Proxy的原理解析

    gRPC是Google開(kāi)始的一個(gè)RPC服務(wù)框架, 是英文全名為Google Remote Procedure Call的簡(jiǎn)稱(chēng),廣泛的應(yīng)用在有RPC場(chǎng)景的業(yè)務(wù)系統(tǒng)中,這篇文章主要介紹了Golang實(shí)現(xiàn)gRPC的Proxy的原理,需要的朋友可以參考下
    2021-09-09
  • gORM操作MySQL的實(shí)現(xiàn)

    gORM操作MySQL的實(shí)現(xiàn)

    本文主要介紹了gORM操作MySQL的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • Goland IDEA項(xiàng)目多開(kāi)設(shè)置方式

    Goland IDEA項(xiàng)目多開(kāi)設(shè)置方式

    這篇文章主要介紹了Goland IDEA項(xiàng)目多開(kāi)設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • go xorm存庫(kù)處理null值問(wèn)題

    go xorm存庫(kù)處理null值問(wèn)題

    這篇文章主要介紹了go xorm存庫(kù)處理null值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 淺談Go切片的值修改是否會(huì)覆蓋數(shù)組的值?

    淺談Go切片的值修改是否會(huì)覆蓋數(shù)組的值?

    本文主要介紹了淺談Go切片的值修改是否會(huì)覆蓋數(shù)組的值,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下?
    2022-02-02
  • 淺談go語(yǔ)言中別名類(lèi)型的使用

    淺談go語(yǔ)言中別名類(lèi)型的使用

    類(lèi)型別名是 Go 1.9 版本添加的新功能,主要用于解決代碼升級(jí)、遷移中存在的類(lèi)型兼容性問(wèn)題,本文主要介紹了go語(yǔ)言中別名類(lèi)型的使用,感興趣的可以了解一下
    2024-01-01
  • go使用net/url包來(lái)解析URL提取主機(jī)部分

    go使用net/url包來(lái)解析URL提取主機(jī)部分

    這篇文章主要為大家介紹了go使用net/url包來(lái)解析URL提取主機(jī)部分實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • Golang并發(fā)編程之Channel詳解

    Golang并發(fā)編程之Channel詳解

    傳統(tǒng)的并發(fā)編程模型是基于線(xiàn)程和共享內(nèi)存的同步訪問(wèn)控制的,共享數(shù)據(jù)受鎖的保護(hù),使用線(xiàn)程安全的數(shù)據(jù)結(jié)構(gòu)會(huì)使得這更加容易。本文將詳細(xì)介紹Golang并發(fā)編程中的Channel,,需要的朋友可以參考下
    2023-05-05

最新評(píng)論