Go中的channel / go例程同步问题

时间:2015-08-28 14:18:40

标签: go

这是一个小例子程序,其中包含我正在努力工作的基本架构/流程。我如何获得所有数字和#34;结束"打印出来的消息?我试过在这里和那里发表密切的陈述,但它要么不起作用,要么我对试图关闭已经关闭的频道感到恐慌......

package main

import (
    "fmt"
    "time"
)

func main() {
    d := make(chan uint)

    go bar(d)

    c1 := make(chan uint)
    c2 := make(chan uint)
    c3 := make(chan uint)

    go foo(c1, d)
    go foo(c2, d)
    go foo(c3, d)

    c1 <- 1
    c2 <- 2
    c3 <- 3

    c1 <- 4
    c2 <- 5
    c3 <- 6

    c1 <- 7
    c2 <- 8
    c3 <- 9
}

func foo(c chan uint, d chan uint) {
    fmt.Println("foo start")

    for stuff := range c {
        time.Sleep(1)
        d <- stuff * 2
    }

    fmt.Println("foo end")
}

func bar(d chan uint) {
    fmt.Println("bar start")

    for stuff := range d {
        fmt.Printf("bar received %d\n", stuff)
    }

    fmt.Println("bar end")
}

我得到的输出看起来像这样。注意最后一组数字和&#34;结束&#34;输出缺失。

foo start
bar start
foo start
foo start
bar received 6
bar received 2
bar received 4
bar received 12
bar received 8
bar received 10

在我的实际节目中,每个&#34; foo&#34;函数正在做过滤和一堆重字符串正则表达式的东西。我需要&#34; bar&#34;函数,因为它具有基于时间戳的重新排序和序列化打印的工作,所以输出不会被隔行扫描。

2 个答案:

答案 0 :(得分:4)

您的程序在所有goroutine完成之前退出。在从foo返回之前,您需要等待barmain goroutines完成。

通常的方法是使用sync.WaitGroup,但由于main不是d频道的制作人,因此您必须确保所有发送在使用第二个WaitGroup(或等效的)关闭之前完成通道。

var (
    fooWG sync.WaitGroup
    barWG sync.WaitGroup
)

func main() {
    d := make(chan uint)

    barWG.Add(1)
    go bar(d)

    c1 := make(chan uint)
    c2 := make(chan uint)
    c3 := make(chan uint)

    fooWG.Add(3)
    go foo(c1, d)
    go foo(c2, d)
    go foo(c3, d)

    c1 <- 1
    c2 <- 2
    c3 <- 3

    c1 <- 4
    c2 <- 5
    c3 <- 6

    c1 <- 7
    c2 <- 8
    c3 <- 9

    // close the channels so the foo goroutines can exit
    close(c1)
    close(c2)
    close(c3)
    fooWG.Wait()

    // all foo are done, so it's safe to close d and wait for bar
    close(d)
    barWG.Wait()
}

func foo(c chan uint, d chan uint) {
    defer fooWG.Done()
    fmt.Println("foo start")

    for stuff := range c {
        time.Sleep(1)
        d <- stuff * 2
    }

    fmt.Println("foo end")
}

func bar(d chan uint) {
    defer barWG.Done()
    fmt.Println("bar start")

    for stuff := range d {
        fmt.Printf("bar received %d\n", stuff)
    }

    fmt.Println("bar end")
}

答案 1 :(得分:1)

JimB的答案肯定有效,但它增加了比代码中实际需要的更多的复杂性。一个简单的完整通道就足以在完成时同步此代码。

此外,通过通道同步,功能不再需要time.Sleep(1)命令:

package main

import (
    "fmt"
    "time"
)

func main() {
    d := make(chan uint)
    complete := make(chan bool)
    go bar(d, complete)

    c1 := make(chan uint)
    c2 := make(chan uint)
    c3 := make(chan uint)

    go foo(c1, d)
    go foo(c2, d)
    go foo(c3, d)

    c1 <- 1
    c2 <- 2
    c3 <- 3

    c1 <- 4
    c2 <- 5
    c3 <- 6

    c1 <- 7
    c2 <- 8
    c3 <- 9

    //If you know the number of inputs, count them to ensure completion
    for i:=0; i < 9; i++{
        <-complete
    }

    //Clean up after yourself, to keep away the memory leaks
    close(c1)
    close(c2)
    close(c3)
    close(d)

    //Verify bar is done and closed correctly
    <-complete
    close(complete)
}

func foo(c chan uint, d chan uint) {
    fmt.Println("foo start")

    for stuff := range c {
        time.Sleep(1)      //Not needed for the program to function
        d <- stuff * 2
    }

    fmt.Println("foo end")
}

func bar(d chan uint, cmp chan bool) {
    fmt.Println("bar start")

    for stuff := range d {
        fmt.Printf("bar received %d\n", stuff)
        cmp <- true
    }

    fmt.Println("bar end")

    //verify that cmp can be closed (all output is done, and d is closed)
    cmp <- true
}