Maison > Article > développement back-end > Quelle est la meilleure pratique dans Golang pour suivre l’état d’achèvement de deux Goroutines au sein d’une troisième Goroutine ?
Quelle est la meilleure pratique dans Golang pour suivre l'état d'achèvement de deux Goroutines dans une troisième Goroutine ? Dans Golang, pour suivre l'état d'achèvement de deux Goroutines et traiter leurs résultats dans un troisième Goroutine, la meilleure pratique consiste à utiliser WaitGroup du package de synchronisation. WaitGroup nous permet d'attendre dans le Goroutine principal la fin des autres Goroutines. Tout d’abord, nous devons créer un objet WaitGroup et appeler la méthode Add dans le Goroutine principal pour définir le nombre de Goroutines en attente. Ensuite, la méthode Done est appelée à la fin de chaque Goroutine pour signaler l'achèvement de cette Goroutine. Enfin, la méthode Wait est appelée dans le troisième Goroutine pour attendre que tous les Goroutines se terminent. De cette façon, nous pouvons suivre et traiter en toute sécurité les résultats des deux Goroutines. Il s'agit de la meilleure pratique de Golang pour suivre l'état d'achèvement de plusieurs Goroutines.
J'ai trois goroutines exécutées simultanément. Deux d'entre eux effectuent un traitement et envoient leurs résultats au canal de résultats. La troisième goroutine « compte » les résultats en lisant les canaux de résultats. Je pourrais utiliser un groupe d'attente pour attendre la fin des deux goroutines de calcul, puis parcourir les canaux de résultats pour comptabiliser les résultats, mais cela ne s'adapte pas et m'oblige à créer un canal de résultats mis en mémoire tampon avec une taille de tampon énorme, ce qui est inacceptable. dans le code de production.
Je souhaite compter les résultats pendant le traitement, mais je ne veux pas quitter le programme avant que tout le comptage ne soit terminé. Quelles sont les bonnes pratiques pour y parvenir dans Go ?
C'est ma méthode actuelle et elle fonctionne très bien. Je me demande s'il existe une meilleure solution car cela semble un peu maladroit ?
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{}{} }
ne correspond à aucune définition raisonnablebien. Vous avez une boucle for
populaire ici :
for { select { case transaction := <-transactions: widgetInventory += transaction case <-salesDone: salesAreDone = true case <-purchasesDone: purchasesAreDone = true default: if salesAreDone && purchasesAreDone { wg.Done() return } } }
Tant qu'il n'y a aucun canal à partir duquel lire, le cas default
sera exécuté. Cela arrive souvent en raison du fonctionnement des chaînes.
Cette version légèrement ajustée du code illustre la "chaleur" de cette boucle. Les résultats exacts varient et peuvent être assez élevés.
Default case ran 27305 times
Vous ne voulez pas le cas select
ing 来自通道时,您不希望出现 default
lorsque vous sélectionnez
un canal, à moins que cette valeur par défaut bloque également quelque chose dedans. Sinon, vous obtiendrez un cycle thermique comme celui-ci.
nil
canaux disponibles pour la sélection Généralement, dans une sélection, vous souhaitez identifier un canal fermé et définir la variable de canal sur nil
; select
永远不会成功地从 nil
select
ne réussira jamais à lire le canal , donc cela "désactive" effectivement le choix. Considérez cette version modifiée
ducode : salesDone
和 purchasesDone
都被“发出信号”,我们 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)Avec ces ajustements au consommateur, nous n'avons plus de boucles chaudes ; nous bloquons toujours jusqu'à ce que les données soient lues depuis le canal. Une fois que
salesDone
et purchasesDone
ont été "signalés", nous fermons (transactions)
. Une fois que nous avons épuisé les transactions
et que main
共享范围。否则,将 transactions
设置为 nil
将写入一个在 goroutine 之间共享的变量。然而在这种情况下,无论如何,这并不重要,因为我们“知道”我们是最后一个从 transactions
est fermée, nous définissons transactions
à zéro. Nous bouclons lorsque transactions
n'est pas nul, ce qui dans ce code signifie que tous les canaux sont
main
. Sinon, définir transactions
sur écrira dans une variable partagée entre les goroutines. Dans ce cas, cependant, cela n'a pas d'importance, car nous "savons" que nous sommes les derniers à lire les transactions
. transactions
的生产。然后你想排空 transactions
。一旦通道关闭并排空,main
select
来执行此操作。而 select
Si vous réfléchissez à ce que vous faites ici, vous devez attendre que les deux producteurs aient fini de s'associer
Vous n'avez pas besoin 🎜 d'avoir un cas pour chaque "travailleur", ce qui est sans doute assez inélégant ; vous devez coder en dur plusieurs travailleurs et gérer le canal "d'achèvement" individuellement. 🎜 🎜Ce que vous devez faire est :🎜
var resultswgsync.WaitGroup
之外,还为消费者添加一个。defer wg.Done()
defer resultswg.Done()
在遍历 transactions
之前:
go func() { defer resultswg.Done() for transaction := range transactions { widgetInventory += transaction } }()
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
多个通道简单得多。事实上,我什至想说,如果您发现自己从多个渠道进行 select
ing,您应该退后一步,确保它对您来说确实有意义。我使用 select
的频率远远低于使用等待组的频率。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!