首頁  >  文章  >  後端開發  >  Golang 中追蹤第三個 Goroutine 中兩個 Goroutine 的完成狀態的最佳實踐是什麼?

Golang 中追蹤第三個 Goroutine 中兩個 Goroutine 的完成狀態的最佳實踐是什麼?

WBOY
WBOY轉載
2024-02-11 14:54:09780瀏覽

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

Golang 中追蹤第三個 Goroutine 中兩個 Goroutine 的完成狀態的最佳實踐是什麼? 在 Golang 中,要追蹤兩個 Goroutine 的完成狀態並在第三個 Goroutine 中處理它們的結果,最佳實踐是使用 sync 套件中的 WaitGroup。 WaitGroup 讓我們在主 Goroutine 中等待其他 Goroutine 的完成。首先,我們需要建立一個 WaitGroup 對象,並在主 Goroutine 中呼叫 Add 方法來設定等待的 Goroutine 數量。然後,在每個 Goroutine 的末尾呼叫 Done 方法來表示該 Goroutine 的完成。最後,在第三個 Goroutine 中呼叫 Wait 方法來等待所有 Goroutine 的完成。這樣,我們就可以安全地追蹤並處理兩個 Goroutine 的結果了。這是 Golang 中追蹤多個 Goroutine 完成狀態的最佳實踐。

問題內容

我有三個並發運行的 goroutine。其中兩個進行一些處理並將其結果發送到結果通道。第三個 goroutine 透過讀取結果通道來「統計」結果。我可以使用 waitgroup 等待兩個計算 goroutine 完成,然後遍歷結果通道來統計結果,但這無法擴展,並且需要我創建一個具有巨大緩衝區大小的緩衝結果通道,這是不可接受的在生產代碼中。

我想在處理發生時統計結果,但在所有統計完成之前我不想退出程式。在 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 循環:

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

只要沒有通道可供讀取,就會執行 default 案例。由於渠道的工作方式,這種情況經常發生。

這個稍作調整的程式碼版本說明了此循環的「熱度」。 確切的結果會有所不同,可能會相當高。

Default case ran 27305 times

selecting 來自通道時,您不希望出現 default 情況,除非該預設也會阻止其中的某些內容。否則你會得到這樣的熱循環。

更好的方法:使用 nilable 頻道進行選擇

通常在選擇中,您想要識別關閉的通道並將通道變數設為nil#; select 永遠不會成功地從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)

透過對消費者的這些調整,我們不再有熱循環;我們總是阻塞直到從通道讀取資料。一旦 salesDonepurchasesDone 都被“發出信號”,我們 close(transactions)。一旦我們耗盡 transactions 並且它被關閉,我們將 transactions 設定為 nil。我們在 transactions 不為 nil 時循環,在這段程式碼中,意味著所有通道都是 nil

微妙但重要的一點:我將通道傳遞給此函數,因此它的參考不與 main 共享範圍。否則,將 transactions 設定為 nil 將寫入一個在 goroutine 之間共享的變數。然而在這種情況下,無論如何,這並不重要,因為我們「知道」我們是最後一個從 transactions 讀取的內容。

更簡單的選項:多個等待群組

如果您考慮您在這裡所做的事情,您需要等到兩個生產者都完成對 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 的频率远远低于使用等待组的频率。

以上是Golang 中追蹤第三個 Goroutine 中兩個 Goroutine 的完成狀態的最佳實踐是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:stackoverflow.com。如有侵權,請聯絡admin@php.cn刪除