Golangはsyncを利用する.WaitGroupによるコヒーレント同期の詳細


きょうどうどうき


実際のプロジェクト開発では、コンカレント同期が必要なシーンが頻繁に発生します.主コンカレントで作成されたコンカレント実行が完了してから、主コンカレントを終了する方法を尋ねる人がよく見られます.たとえば、次のコードでは、100コンカレントで同時印刷を実現する例を示します.
package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 100 ; i++{
		go func(i int) {
			fmt.Println("Goroutine ",i)
		}(i)
	}
}

以上のコードを実行すると、出力が見えない可能性が高いか、一部のコヒーレンスのみが実行される可能性があります.この100のコヒーレンスがまだ実行されていない可能性があります.または、一部のコヒーレンスが実行されていない可能性があります.メインコヒーレンスが終了すると、他のすべてのコヒーレンスが終了します.解決策はmain関数の最後にsleep()待機を追加することです.
package main

import (
    "fmt"
    "time"
)

func main() {
  for i := 0; i < 100 ; i++{
		go func(i int) {
			fmt.Println("Goroutine ",i)
		}(i)
	}
    time.Sleep(time.Second * 1) //  1 , 
}

メインスレッドはgoroutineの実行が完了するのを待つために、プログラムの最後にtimeを使用せざるを得ない.Sleep()はしばらく眠って、他のスレッドが十分に実行されるのを待っています.簡単なコードの場合、複数のforループは1秒以内に実行できます.time.Sleep()も所望の効果を達成できます.しかし、実際の生活の多くのシーンでは、1秒が足りず、forループ内のコードの実行時間の長さを予知できないことが多い.このときtimeは使用できません.Sleep()が待機操作を完了しました.これは完璧な解決策ではありません.この2つの協力に複雑な操作が含まれている場合、時間がかかる可能性があります.睡眠時間がどのくらいかかるかは判断できません.もちろん、パイプで同期することができます.
package main

import (
    "fmt"
)

func main() {

    ch := make(chan struct{})
    count := 100 // count  
    
   for i := 0; i < 100 ; i++{
		go func(i int) {
			fmt.Println("Goroutine ",i)
                        ch 

上の解決策は比較的完璧な方案で、まずパイプを使うことが私たちの目的を達成することができて、その上目的を達成することができるだけではなくて、また非常に完璧に目的を達成することができます.しかし、パイプはここでは単純な同期処理として使用されるだけでなく、ここでパイプを使用するのは実際には適切ではないため、少し役に立たないように見えます.さらに、1万、10万、さらに多くのforサイクルがあると仮定して、同じサイズのパイプを申請し、メモリにも大きなコストがかかります.Goはsyncを使用するより簡単な方法を提供した.WaitGroup.WaitGroupはその名の通り、一連の操作が完了するのを待つために使用されます.WaitGroupの内部には、未完了の操作数を記録するカウンタが実装されており、カウントを追加するための3つの方法が提供されています.Done()は、操作終了時に呼び出し、カウントを1つ減らすために使用されます.Wait()は、すべての操作が終了するのを待つために使用されます.すなわち、カウントが0になり、カウントが0でない場合に待機し、カウントが0の場合にすぐに戻ります.
package main

import (
    "fmt"
    "sync"
)

func main() {

    var wg sync.WaitGroup

    wg.Add(100) //  , 2 
     for i := 0; i < 100 ; i++{
		go func(i int) {
			fmt.Println("Goroutine ",i)
                       wg.Done() //  , 
		}(i)
	}

    wg.Wait() //  , 0
}

可視用sync.WaitGroupは最も簡単な方法です.

注意事項


1.カウンタは負の値ではありません.Add()を使用してwgに負の値を設定することはできません.そうしないと、コードがエラーになります.
panic: sync: negative WaitGroup counter
 
goroutine 1 [running]:
sync.(*WaitGroup).Add(0xc042008230, 0xffffffffffffff9c)
    D:/Go/src/sync/waitgroup.go:25 +0x1d0
main.main()
    D:/code/go/src/test-src/2-Package/sync/waitgroup/main.go:10 +0x54

同様にDone()を使ってもカウンタを負数にしないように注意しましょう.
2.WaitGroupオブジェクトは参照タイプではありませんWaitGroupオブジェクトは参照タイプではありません.関数を介して値を渡すときにアドレスを使用する必要があります.
func main() {
    wg := sync.WaitGroup{}
    wg.Add(100)
    for i := 0; i < 100; i++ {
        go f(i, &wg)
    }
    wg.Wait()
}
 
//  , 
func f(i int, wg *sync.WaitGroup) { 
    fmt.Println(i)
    wg.Done()
}

学び合う.共に進歩する.