【问题标题】:Closing a Go channel, and syncing a go routine关闭 Go 频道,并同步 Go 例程
【发布时间】:2018-02-18 05:36:21
【问题描述】:

我无法在 go 中终止我的 WaitGroup,因此无法退出范围循环。谁能告诉我为什么。或者是一种更好的方法来限制 go 例程的数量,同时仍然能够在 chan 关闭时退出!

我见过的大多数示例都与静态类型的 chan 长度有关,但该通道会因其他进程而动态调整大小。

打印示例中的打印语句 ("DONE!") 显示 testValProducer 打印了正确的次数,但代码从未到达 ("--EXIT--"),这意味着 wg.Wait 仍然以某种方式阻塞.

type TestValContainer chan string

func StartFunc(){
testValContainer            := make(TestValContainer)
go func(){testValContainer <- "string val 1"}()
go func(){testValContainer <- "string val 2"}()
go func(){testValContainer <- "string val 3"}()
go func(){testValContainer <- "string val 4"}()
go func(){testValContainer <- "string val 5"}()
go func(){testValContainer <- "string val 6"}()
go func(){testValContainer <- "string val 7"}()
wg  := sync.WaitGroup{}

// limit the number of worker goroutines
for i:=0; i < 3; i++ {
    wg.Add(1)
    go func(){
        v := i
        fmt.Printf("launching %v", i)
        for str := range testValContainer{
            testValProducer(str, &wg)
        }
        fmt.Println(v, "--EXIT --")  // never called
    }()
}

wg.Wait()
close(testValContainer)

}


func get(url string){
    http.Get(url)
    ch <- getUnvisited()
}


func testValProducer(testStr string, wg *sync.WaitGroup){
    doSomething(testStr)
    fmt.Println("done !") // called
    wg.Done() // NO EFFECT??
}

【问题讨论】:

  • 没有可以动态调整大小的通道,但代码中没有任何内容表明您是如何尝试这样做的。您正在复制您的 WaitGroup,go vet 会为您指出此错误
  • 抱歉,动态调整大小是指无缓冲的频道“去检查”?
  • 无缓冲通道没有缓冲区,因此它的“大小”永远不会是 0。vet 是内置工具之一,在此代码上运行它。
  • 谢谢我已经更新了,问题还没解决问题,页面脚本仍然无法到达打印语句
  • 你应该只为每个 goroutine 调用wg.Done,但你为从testValContainer 收到的每个值调用它。我不确定逻辑应该是什么,因此很难提供正确的答案。你想完成什么。

标签: go concurrency channel goroutine


【解决方案1】:

在您的示例中,您有两个错误:

  1. 您在每个工作线程的循环中调用wg.Done,而不是在工作线程的末尾(就在它完成之前)。对wg.Done 的调用必须与wg.Add(1)s 一对一匹配。
  2. 修复此问题后,会出现死锁,主线程正在等待工作线程完成,而工作线程区域则等待主线程关闭输入通道。

如果您将生产者端与消费者端更清晰地分开,逻辑将更清晰,更容易理解。为每一侧运行一个单独的 goroutine。示例:

// Producer side (only write and close allowed).
go func() {
    testValContainer <- "string val 1"
    testValContainer <- "string val 2"
    testValContainer <- "string val 3"
    testValContainer <- "string val 4"
    testValContainer <- "string val 5"
    testValContainer <- "string val 6"
    testValContainer <- "string val 7"
    close(testValContainer) // Signals that production is done.
}()

// Consumer side (only read allowed).
for i:=0; i < 3; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        v := i
        fmt.Printf("launching %v", i)
        for str := range testValContainer {
            doSomething(str)
        }
        fmt.Println(v, "--EXIT --")
    }()
}
wg.Wait()

如果项目是从其他来源生成的,可能是 goroutines 的集合,您仍然应该有:1) 一个单独的 goroutine 或逻辑某处监督该生产并在完成后调用 close,或 2)让您的主线程等待生产端完成(例如,使用WaitGroup 等待生产者 goroutines)并在等待消费端之前关闭通道

如果您考虑一下,无论您如何安排逻辑,您都需要有一些“旁通道”方式来检测,在一个同步的地方,没有更多的消息正在产生。否则你永远不知道什么时候应该关闭频道。

换句话说,您不能等待消费者端的范围循环完成来触发close,因为这会导致catch 22。

【讨论】:

    【解决方案2】:

    我可能会做这样的事情,它让一切都很容易理解。我定义了一个结构,它实现了一个信号量来控制活跃的 Go 例程的数量……并允许我在通道进入时读取它们。

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type TestValContainer struct {
        wg   sync.WaitGroup
        sema chan struct{}
        data chan int
    }
    
    func doSomething(number int) {
        fmt.Println(number)
    }
    
    func main() {
        //semaphore limit 10 routines at time
        tvc := TestValContainer{
            sema: make(chan struct{}, 10),
            data: make(chan int),
        }
    
        for i := 0; i <= 100; i++ {
            tvc.wg.Add(1)
            go func(i int) {
                tvc.sema <- struct{}{}
                defer func() {
                    <-tvc.sema
                    tvc.wg.Done()
                }()
    
                tvc.data <- i
            }(i)
        }
        // wait in the background so that waiting and closing the channel dont
        // block the for loop below
        go func() {
            tvc.wg.Wait()
            close(tvc.data)
        }()
        // get channel results
        for res := range tvc.data {
            doSomething(res)
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2020-12-28
      • 2021-03-14
      • 2016-08-09
      • 2011-05-26
      • 2015-08-02
      • 1970-01-01
      • 1970-01-01
      • 2019-10-14
      • 1970-01-01
      相关资源
      最近更新 更多