文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-02-11 14:43

关注

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 通道进行选择

通常在选择中,您想要识别关闭的通道并将通道变量设置为 nilselect 永远不会成功地从 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 为每个“工人”都有一个案例,可以说是相当不优雅的;您必须对多个工作人员进行硬编码并单独处理“完成”通道。

您需要做的是:

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

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 的完成状态的最佳实践是什么?的详细内容,更多请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯