ホームページ  >  記事  >  バックエンド開発  >  3 番目のゴルーチン内の 2 つのゴルーチンの完了ステータスを追跡するための Golang のベスト プラクティスは何ですか?

3 番目のゴルーチン内の 2 つのゴルーチンの完了ステータスを追跡するための Golang のベスト プラクティスは何ですか?

WBOY
WBOY転載
2024-02-11 14:54:09782ブラウズ

Golang 中跟踪第三个 Goroutine 中两个 Goroutine 的完成状态的最佳实践是什么?

Golang で 3 番目の Goroutine で 2 つの Goroutine の完了ステータスを追跡するためのベスト プラクティスは何ですか? Golang では、2 つのゴルーチンの完了ステータスを追跡し、その結果を 3 番目のゴルーチンで処理するには、同期パッケージの WaitGroup を使用するのがベスト プラクティスです。 WaitGroup を使用すると、メインの Goroutine で他の Goroutine の完了を待つことができます。まず、WaitGroup オブジェクトを作成し、メインの Goroutine で Add メソッドを呼び出して待機中の Goroutine の数を設定する必要があります。次に、各ゴルーチンの最後に Done メソッドが呼び出され、そのゴルーチンの完了を通知します。最後に、3 番目のゴルーチンで Wait メソッドが呼び出され、すべてのゴルーチンが完了するまで待機します。こうすることで、両方のゴルーチンの結果を安全に追跡して処理できます。これは、複数の Goroutine の完了ステータスを追跡するための Golang のベスト プラクティスです。

質問内容

3つのゴルーチンを同時に実行しています。そのうちの 2 つは何らかの処理を実行し、結果を結果チャネルに送信します。 3 番目のゴルーチンは、結果チャネルを読み取ることによって結果を「カウント」します。 waitgroup を使用して 2 つの計算ゴルーチンが完了するのを待ってから、結果チャネルを反復処理して結果を集計することもできますが、これでは拡張できず、巨大なバッファ サイズでバッファリングされた結果チャネルを作成する必要があり、これは受け入れられません。プロダクションコードで。

処理の実行中に結果をカウントしたいのですが、すべての統計が完了する前にプログラムを終了したくありません。 Go でこれを達成するためのベスト プラクティスは何ですか?

これは私の現在の方法であり、うまく機能します。これは少し不格好に見えるので、より良い方法があるかどうか疑問に思っていますか?

package main

import (
    "fmt"
    "sync"
)

type T struct{}

func main() {
    var widgetInventory int = 1000
    transactions := make(chan int, 100)
    salesDone := make(chan T)
    purchasesDone := make(chan T)
    var wg sync.WaitGroup
    fmt.Println("Starting inventory count = ", widgetInventory)

    go makeSales(transactions, salesDone)
    go newPurchases(transactions, purchasesDone)

    wg.Add(1)

    go func() {
        salesAreDone := false
        purchasesAreDone := false

        for {
            select {
            case transaction := <-transactions:
                widgetInventory += transaction
            case <-salesDone:
                salesAreDone = true
            case <-purchasesDone:
                purchasesAreDone = true
            default:
                if salesAreDone && purchasesAreDone {
                    wg.Done()
                    return
                }
            }
        }
    }()

    wg.Wait()
    fmt.Println("Ending inventory count = ", widgetInventory)
}

func makeSales(transactions chan int, salesDone chan T) {
    for i := 0; i < 3000; i++ {
        transactions <- -100
    }

    salesDone <- struct{}{}
}

func newPurchases(transactions chan int, purchasesDone chan T) {
    for i := 0; i < 3000; i++ {
        transactions <- 100
    }

    purchasesDone <- struct{}{}
}

解決策

適切な定義に適合しません良い。ここにホット for ループがあります:

リーリー

読み取り元のチャネルがない限り、default ケースが実行されます。これは、チャネルの仕組みが原因で頻繁に発生します。

コードのこのわずかに調整されたバージョンは、このループの「熱さ」を示しています。 正確な結果はさまざまであり、かなり高い値になる可能性があります。

リーリー

デフォルトで何かがブロックされていない限り、チャネルから select するときに default の状況は望ましくありません。そうしないと、このような熱サイクルが発生します。

より良いアプローチ: nil可能なチャネルを選択に使用します

通常、選択では閉じたチャネルを特定し、チャネル変数を nil に設定します。selectnil# からは決して成功しません。 ## チャネルはコンテンツを読み取るため、これにより事実上選択が「無効」になります。

コードの修正バージョン : を考えてみましょう。 リーリー

コンシューマーに対するこれらの調整により、ホット ループはなくなり、データがチャネルから読み取られるまで常にブロックされます。

salesDonepurchasesDone の両方が「通知」されると、close(transactions) になります。 transactions を使い果たし、 がクローズされると、transactions を nil に設定します。 transactions が nil でない場合にループします。このコードでは、すべてのチャネルが nil であることを意味します。

微妙だが重要な点: この関数にチャネルを渡しているので、その参照は

main とスコープを共有しません。それ以外の場合、transactionsnil に設定すると、ゴルーチン間で共有される変数に書き込まれます。ただし、この場合は、transactions から最後に読み取ることが「わかっている」ため、それは問題ではありません。

よりシンプルなオプション: 複数の待機グループ

ここで何をしているのかを考えると、両方のプロデューサーが

トランザクション の生成を完了するまで待つ必要があります。次に、transactions を排出します。チャネルが閉じられて排出されると、main は合計が完了したことを認識します。

この操作を実行するのに

select は必要ありません。そして、select が各 "ワーカー" にケースを持たせるのは、おそらくかなり洗練されていないため、複数のワーカーをハードコーディングし、"完了" チャネルを個別に処理する必要があります。

あなたがしなければならないことは次のとおりです:

  • 除了为生产者使用一个 var resultswgsync.WaitGroup 之外,还为消费者添加一个。
  • 生产者 defer wg.Done()
  • 消费者 defer resultswg.Done() 在遍历 transactions 之前:
    go func() {
        defer resultswg.Done()
        for transaction := range transactions {
            widgetInventory += transaction
        }
    }()
  • main 处理等待生产者、关闭事务以结束范围,然后等待消费者:
    wg.Wait()
    close(transactions)
    resultswg.Wait()

以这种方式编码,最终会变得简短而甜蜜

package main

import (
    "fmt"
    "sync"
)

func main() {
    var widgetInventory int = 1000
    transactions := make(chan int, 100)

    var wg, resultswg sync.WaitGroup
    fmt.Println("Starting inventory count = ", widgetInventory)
    wg.Add(2)

    go makeSales(transactions, &wg)
    go newPurchases(transactions, &wg)
    resultswg.Add(1)
    go func() {
        defer resultswg.Done()
        for transaction := range transactions {
            widgetInventory += transaction
        }
    }()

    wg.Wait()
    close(transactions)
    resultswg.Wait()
    fmt.Println("Ending inventory count = ", widgetInventory)
}

func makeSales(transactions chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 3000; i++ {
        transactions <- -100
    }

}

func newPurchases(transactions chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 3000; i++ {
        transactions <- 100
    }

}

您可以在这里看到,在此模式中可以有任意数量的生产者;您只需为每个生产者添加 wg.Add(1) 即可。

当我不知道每个工作人员会返回多少结果时,我一直使用这种模式来并行化工作。我发现它很容易理解,并且比尝试 select 多个通道简单得多。事实上,我什至想说,如果您发现自己从多个渠道进行 selecting,您应该退后一步,确保它对您来说确实有意义。我使用 select 的频率远远低于使用等待组的频率。

以上が3 番目のゴルーチン内の 2 つのゴルーチンの完了ステータスを追跡するための Golang のベスト プラクティスは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はstackoverflow.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。