ホームページ > バックエンド開発 > Golang > golang のメモリリークの原因は何ですか?

golang のメモリリークの原因は何ですか?

青灯夜游
リリース: 2023-01-10 17:45:48
オリジナル
2397 人が閲覧しました

リークの理由は: 1. time.After() の使用。time.After(duration x) は NewTimer() を生成します。duration x が期限切れになる前に、新しく作成されたタイマーは生成されません。 GC 、GC は期限切れ後にのみ発生します; 2. time.NewTicker リソースが時間内に解放されません; 3. 選択のブロック; 4. チャネルのブロック; 5. 多すぎるゴルーチンの適用、ゴルーチンのブロック; 6. スライスなどが原因です。

golang のメモリリークの原因は何ですか?

このチュートリアルの動作環境: Windows 7 システム、GO バージョン 1.18、Dell G3 コンピューター。

golang がメモリ リークを引き起こしやすいいくつかの状況

1. タイマーの不適切な使用

1.1 time.After() の使用

デフォルトの time.After() では、毎回 time.After(duration x) が NewTimer を生成するため、メモリ リークの問題が発生します。 ( )、新しく作成されたタイマーは、期間 x が期限切れになる前に GC されず、期限切れ後にのみ GC されます。

時間が経つにつれて、特に期間が長くなると、このメソッドはリソースを積極的に解放します。2 つの違いは自分で確認するか、以前の記事 https://blog.csdn.net/weixin_38299404/article/details を読んでください。 /119352884

for true {
	select {
	case <-time.After(time.Minute * 3):
    // 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 を使用する場合 (存在する場合)これは状況を完全にはカバーしておらず、処理のためのデフォルトのブランチがない場合、最終的にメモリ リークにつながります

2.1 goroutine がブロックされる状況
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)
	}
}
ログイン後にコピー
上記の状況により ch3 の消費がブロックされ、メモリ リークが発生します

2.2 ループ アイドリングにより CPU サージが発生します
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)
    }
}
ログイン後にコピー
上記の for ループ条件がデフォルトに達すると、ループ アイドリングが発生し、最終的に CPU サージにつながります

3. チャネル ブロッキング #チャネル ブロッキングは主に 2 つの状況に分けられます: 書き込みブロッキングと読み取りブロッキング

空のチャネル

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)
}
ログイン後にコピー

書き込みブロッキング

バッファリングされていないチャネルのブロッキングは、通常、読み取りがないため、書き込み操作がブロックされます。

    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)
    }
    ログイン後にコピー

バッファがいっぱいであるため、バッファされたチャネルがブロックされます。、書き込み操作がブロックされました

    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)
    }
    ログイン後にコピー
  • 読み取りがブロックされました

チャネルからのデータの読み取りを待機していますが、データを書き込むゴルーチンがありませんでした

    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)
    }
    ログイン後にコピー

4. goroutine によるメモリリーク

4.1 多すぎる goroutine の適用

たとえば、次のように適用します。 for ループ内のゴルーチンが多すぎると、それらを時間内に解放しないとメモリ リークが発生します

##4.2 ゴルーチンのブロック

4.2. 1 I /O 問題

I/O 接続にタイムアウトが設定されていないため、ゴルーチンは待機し続け、コードはブロックされ続けます。

4.2.2 ミューテックス ロックが解放されない
ゴルーチンがロック リソースを取得できないため、ゴルーチンがブロックされます
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.2.3 デッドロック
プログラムがデッドロックすると、他のゴルーチンもブロックされます
//协程拿到锁未释放,其他协程获取锁会阻塞
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 4 waitgroup の不適切な使用
waitgroup の Add、Done、wait の数が一致しないと、wait が待ち続けます

5スライス リークによるメモリ

#2 つのスライスがアドレスを共有し、一方がグローバル変数であり、他方が GC できない場合;スライスを追加した後に使用されています掃除もせずに。
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)
}
ログイン後にコピー

#6. 配列値の転送

配列は Golang の基本データ型であるため、各配列が占有するメモリ空間の量は異なります。サイクルは相互に干渉しないのでメモリリークが起こりにくいですが、配列を仮引数として渡す場合は時刻値のコピーが続きます 複数のゴルーチンから関数が呼び出され配列が大きすぎると、メモリ使用量が急増する原因になります。
var a []int
 
func test(b []int) {
        a = b[:3]
        return
}
ログイン後にコピー
したがって、大きな配列が仮パラメータのシナリオに配置される場合、メモリ使用量の短期的な急増を避けるために、通常はスライスまたはポインタを使用して配列を渡します。 ##Go ビデオ チュートリアル、プログラミング教育

]

以上がgolang のメモリリークの原因は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
関連するチュートリアル
人気のおすすめ
最新のコース
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート