Heim  >  Artikel  >  Backend-Entwicklung  >  Was ist die beste Vorgehensweise in Golang, um den Abschlussstatus von zwei Goroutinen innerhalb einer dritten Goroutine zu verfolgen?

Was ist die beste Vorgehensweise in Golang, um den Abschlussstatus von zwei Goroutinen innerhalb einer dritten Goroutine zu verfolgen?

WBOY
WBOYnach vorne
2024-02-11 14:54:09844Durchsuche

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

Was ist die beste Vorgehensweise in Golang, um den Abschlussstatus von zwei Goroutinen in einer dritten Goroutine zu verfolgen? Um in Golang den Abschlussstatus von zwei Goroutinen zu verfolgen und ihre Ergebnisse in einer dritten Goroutine zu verarbeiten, besteht die beste Vorgehensweise darin, WaitGroup aus dem Synchronisierungspaket zu verwenden. WaitGroup ermöglicht es uns, in der Haupt-Goroutine auf die Fertigstellung anderer Goroutinen zu warten. Zuerst müssen wir ein WaitGroup-Objekt erstellen und die Add-Methode in der Haupt-Goroutine aufrufen, um die Anzahl der wartenden Goroutinen festzulegen. Anschließend wird am Ende jeder Goroutine die Done-Methode aufgerufen, um den Abschluss dieser Goroutine zu signalisieren. Schließlich wird in der dritten Goroutine die Wait-Methode aufgerufen, um auf den Abschluss aller Goroutinen zu warten. Auf diese Weise können wir die Ergebnisse beider Goroutinen sicher verfolgen und verarbeiten. Dies ist die beste Vorgehensweise in Golang, um den Abschlussstatus mehrerer Goroutinen zu verfolgen.

Frageninhalt

Ich habe drei Goroutinen gleichzeitig laufen lassen. Zwei von ihnen führen eine Verarbeitung durch und senden ihre Ergebnisse an den Ergebniskanal. Die dritte Goroutine „zählt“ die Ergebnisse, indem sie die Ergebniskanäle liest. Ich könnte eine Wartegruppe verwenden, um auf den Abschluss der beiden Berechnungs-Goroutinen zu warten und dann über die Ergebniskanäle zu iterieren, um die Ergebnisse zu zählen, aber das lässt sich nicht skalieren und erfordert, dass ich einen gepufferten Ergebniskanal mit einer riesigen Puffergröße erstelle, was inakzeptabel ist im Produktionscode.

Ich möchte die Ergebnisse zählen, während die Verarbeitung läuft, aber ich möchte das Programm nicht beenden, bevor die Zählung abgeschlossen ist. Was sind die Best Practices, um dies in Go zu erreichen?

Das ist meine aktuelle Methode und sie funktioniert großartig. Ich frage mich, ob es einen besseren Weg gibt, da das etwas umständlich erscheint?

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{}{}
}

Die Lösung

passt keiner vernünftigen Definitiongut. Sie haben hier eine beliebte for-Schleife:

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

Der default-Fall wird ausgeführt, solange kein Kanal zum Lesen vorhanden ist. Dies geschieht aufgrund der Art und Weise, wie Kanäle funktionieren, häufig.

Diese leicht angepasste Version des Codes veranschaulicht die „Hitze“ dieser Schleife. Die genauen Ergebnisse variieren und können recht hoch ausfallen.

Default case ran 27305 times

Sie möchten den selecting 来自通道时,您不希望出现 default-Fall nicht haben, wenn Sie aus einem Kanal auswählen, es sei denn, diese Standardeinstellung blockiert auch etwas darin. Sonst kommt es zu solchen Temperaturwechseln.

Besser: Verwenden Sie nilfähige Kanäle zur Auswahl

Normalerweise möchten Sie in einer Auswahl einen geschlossenen Kanal identifizieren und die Kanalvariable auf nilselect 永远不会成功地从 nil

setzen; select wird nie erfolgreich aus dem

-Kanal lesen, sodass die Auswahl effektiv „deaktiviert“ wird. Betrachten Sie diese modifizierte Version

des

Codes: salesDonepurchasesDone 都被“发出信号”,我们 close(transactions)。一旦我们耗尽 transactions 并且它被关闭,我们将 transactions 设置为 nil。我们在 transactions 不为 nil 时循环,在这段代码中,意味着所有通道都是 nil

go func(transactions chan int, salesDone <-chan T, purchasesDone <-chan T) {
        defer wg.Done()
        for transactions != nil {
            select {
            case transaction, ok := <-transactions:
                if ok {
                    widgetInventory += transaction
                } else {
                    transactions = nil
                }
            case <-salesDone:
                salesDone = nil
                if purchasesDone == nil {
                    close(transactions)
                }
            case <-purchasesDone:
                purchasesDone = nil
                if salesDone == nil {
                    close(transactions)
                }

            }
        }
    }(transactions, salesDone, purchasesDone)

Mit diesen Anpassungen am Verbraucher haben wir keine Hot Loops mehr; wir blockieren immer, bis Daten aus dem Kanal gelesen werden. Sobald sowohl salesDone als auch purchasesDone „signalisiert“ wurden, schließen wir (Transaktionen). Sobald wir transactions erschöpft haben

und main 共享范围。否则,将 transactions 设置为 nil 将写入一个在 goroutine 之间共享的变量。然而在这种情况下,无论如何,这并不重要,因为我们“知道”我们是最后一个从 transactions es geschlossen ist, setzen wir transactions auf Null. Wir durchlaufen eine Schleife, wenn transactions nicht Null ist, was in diesem Code bedeutet, dass alle Kanäle

sind.

Subtiler, aber wichtiger Punkt: Ich übergebe einen Kanal an diese Funktion, damit ihre Referenz den Gültigkeitsbereich nicht mit main teilt. Andernfalls wird durch Setzen von transactions auf

in eine Variable geschrieben, die von Goroutinen gemeinsam genutzt wird. In diesem Fall spielt es jedoch sowieso keine Rolle, da wir „wissen“, dass wir die letzten sind, die aus transactions lesen. transactions 的生产。然后你想排空 transactions。一旦通道关闭并排空,main

Einfachere Option: mehrere Wartegruppen

select 来执行此操作。而 selectWenn Sie darüber nachdenken, was Sie hier tun, müssen Sie warten, bis beide Produzenten die Paarung abgeschlossen haben

, bevor Sie wissen, dass die Summe vollständig ist.

Sie müssen nicht 🎜 für jeden „Arbeiter“ einen Fall haben, was wohl ziemlich unelegant ist; Sie müssen mehrere Arbeiter fest codieren und den „Abschluss“-Kanal einzeln behandeln. 🎜 🎜Was Sie tun müssen, ist:🎜
  • 除了为生产者使用一个 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 的频率远远低于使用等待组的频率。

Das obige ist der detaillierte Inhalt vonWas ist die beste Vorgehensweise in Golang, um den Abschlussstatus von zwei Goroutinen innerhalb einer dritten Goroutine zu verfolgen?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen