Go - 如何知道输出通道何时完成

时间:2014-12-21 21:48:31

标签: input concurrency go output channel

我试图跟随Rob Pike的例子来谈谈“并发不是并行性”#39;并做了这样的事情: 我作为工作人员从输入通道读取,执行一些处理然后通过输出通道发送结果,我开始了很多例程。

然后我开始另一个go例程,从一些源读取数据并通过其输入通道将其发送给工作人员。 最后,我想迭代输出通道中的所有结果并对它们做一些事情。 问题在于,由于工作在工人之间分配,我不知道所有工人何时完成,所以我可以停止询问输出渠道以获得更多结果,我的程序可以正常结束。

了解员工何时将结果发送到输出渠道的最佳做法是什么?

3 个答案:

答案 0 :(得分:3)

我个人喜欢使用sync.WaitGroup。 waitgroup是一个同步计数器,有三种方法 - Wait()Done()Add()。你做的是增加waitgroup的计数器,将它传递给工作人员,让他们在完成后调用Done()。然后你只需阻塞另一端的等待组,并在它们全部完成时关闭输出通道,导致输出处理器退出。

基本上:

// create the wait group
wg := sync.WaitGroup{}

// this is the output channel
outchan := make(chan whatever)

// start the workers
for i := 0; i < N; i++ {
   wg.Add(1) //we increment by one the waitgroup's count

   //the worker pushes data onto the output channel and calls wg.Done() when done
   go work(&wg, outchan)
}

// this is our "waiter" - it blocks until all workers are done and closes the channel
go func() {
  wg.Wait()
  close(outchan)
}()

//this loop will exit automatically when outchan is closed
for item := range outchan {
   workWithIt(item)
}

// TADA!

答案 1 :(得分:0)

我可以先澄清你的术语:对渠道末端的误解可能会导致问题。你询问“输出通道”和“输入通道”。哪有这回事;只有频道

每个通道都有两个结束:输出(写入)结束,输入(读取)结束。我会假设这就是你的意思。

现在回答你的问题。

采取最简单的情况:您只有一个发件人goroutine写入一个频道,并且您只有一个工作者goroutine从另一端读取,并且该频道有零缓冲。发件人goroutine将阻止它写入每个项目,直到该项目被消费。通常这种情况在第一次发生很快。一旦第一个项目传递给工作人员,工作人员将忙碌,发件人必须等待第二个项目可以传递。因此,乒乓效应如下:作者或读者将忙,但不是两者兼而有之。在Rob Pike描述的意义上,goroutines将是并发,但并不总是实际并行执行

如果您有许多工作人员goroutines从频道读取(并且其输入端由所有人共享),发件人最初可以将一个项目分配给每个工作人员,但是它必须在工作时等待(类似于上面描述的乒乓球案例)。最后,当发件人发送了所有项目后,它已完成工作。但是,读者可能还没有完成他们的工作。有时我们关心发件人提前完成,有时我们不关心。知道何时发生这种情况最容易通过WaitGroup完成(参见 Not_a_Golfer 的答案和my answer to a related question)。

有一个稍微复杂的替代方案:您可以使用返回通道来完成信号,而不是WaitGroup。这并不难,但在这种情况下,WaitGroup是首选,更简单。

如果信道要包含一个缓冲区,则发送方发送其最后一个项目的点会更快发生。在极限情况下,通道每个工人有一个缓冲空间;这将允许发件人很快完成,然后可能继续使用其他东西。 (任何比这更缓冲都会浪费)。

发送方的这种分离允许完全异步的行为模式,受到使用其他技术堆栈的人的喜爱(Node-JS和JVM spring)。与他们不同,Go不会需要你这样做,但你可以选择。

回到90年代早期,作为批量同步并行(BSP)策略工作的副作用,Leslie Valiant证明有时非常简单的同步策略可以很便宜。关键因素是需要足够的并行松弛(a.k.a。过剩的并行性)来保持处理器内核的繁忙。这意味着必须有足够的其他工作要做,以便在一段时间内阻止任何特定的goroutine无关紧要。

奇怪的是,这可能意味着使用较少数量的goroutine可能需要更多关心,而不是使用更大的数字。

理解过度并行的影响是有用的:如果整个网络具有过多的并行性,通常没有必要付出额外的努力来使所有内容异步,因为CPU内核会以任何方式忙碌。

因此,虽然了解如何等到发件人完成后很有用,但更大的应用程序可能不需要您以同样的方式关注。

作为最后一个脚注,WaitGroup是BSP中使用的意义上的障碍。通过组合障碍和渠道,您可以使用BSP和CSP。

答案 2 :(得分:0)

var Z = "Z"

func Loop() {
    sc := make(chan *string)
    ss := make([]string, 0)
    done := make(chan struct{}, 1)
    go func() {
        //1 QUERY
        slice1 := []string{"a", "b", "c"}
        //2 WG INIT
        var wg1 sync.WaitGroup
        wg1.Add(len(slice1))
        //3 LOOP->
        loopSlice1(slice1, sc, &wg1)
        //7 WG WAIT<-
        wg1.Wait()
        sc <- &Z
        done <- struct{}{}
    }()

    go func() {
        var cc *string
        for {
            cc = <-sc
            log.Infof("<-sc %s", *cc)
            if *cc == Z {
                break
            }
            ss = append(ss, *cc)
        }
    }()
    <-done
    log.Infof("FUN: %#v", ss)
}

func loopSlice1(slice1 []string, sc chan *string, wg1 *sync.WaitGroup) {
    for i, x := range slice1 {
        //4 GO
        go func(n int, v string) {
            //5 WG DONE
            defer wg1.Done()
            //6 DOING
            //[1 QUERY
            slice2 := []string{"X", "Y", "Z"}
            //[2 WG INIT
            var wg2 sync.WaitGroup
            wg2.Add(len(slice2))
            //[3 LOOP ->
            loopSlice2(n, v, slice2, sc, &wg2)
            //[7 WG WAIT <-
            wg2.Wait()
        }(i, x)
    }
}

func loopSlice2(n1 int, v1 string, slice2 []string, sc chan *string, wg2 *sync.WaitGroup) {
    for j, y := range slice2 {
        //[4 GO
        go func(n2 int, v2 string) {
            //[5 WG DONE
            defer wg2.Done()
            //[6 DOING
            r := fmt.Sprintf("%v%v %v,%v", n1, n2, v1, v2)
            sc <- &r
        }(j, y)
    }
}