关闭Go通道,并同步go例程

时间:2017-09-09 12:55:15

标签: go concurrency channel goroutine

我无法终止我的WaitGroup,因此无法退出范围循环。任何人都可以告诉我原因。或者是一种更好的方法来限制行程的数量,同时仍然能够在陈关闭时退出!

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

打印示例中的print语句(" DONE!"),显示testValProducer打印正确的次数,但代码永远不会到达(" - EXIT - &# 34;)这意味着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??
}

2 个答案:

答案 0 :(得分:0)

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

  1. 您在每个工作线程的循环中调用wg.Done而不是在工作线程的末尾(在它完成之前)。对wg.Done的来电必须与wg.Add(1) s一对一匹配。
  2. 解决了这个问题后,主线程正在等待工作线程完成的死锁,而工作线程区域等待主线程关闭输入通道。
  3. 如果将制作者方面与消费者方面分开,则逻辑将更清晰,更容易理解。为每一方运行一个单独的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()
    

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

    如果您考虑一下,无论您如何安排逻辑,您都需要某些&#34; side-channel&#34;在一个单一的同步位置检测不再生成消息的方法。否则你永远不知道什么时候应该关闭频道。

    换句话说,您不能等待消费者方面的范围循环完成以触发close,因为这会导致捕获22。

答案 1 :(得分:0)

我可能会做这样的事情,它让一切都很容易遵循。我定义了一个实现信号量的结构来控制激活的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)
    }

}