通道提前终止

Channels terminate prematurely

我正在为每个执行转换的管道制作一系列 go 例程的原型。例程在所有数据通过之前终止。

我已经查阅了 Donavan 和 Kernighan 的书并在 Google 上搜索了解决方案。

这是我的代码:

package main

import (
    "fmt"
    "sync"
)

func main() {
    a1 := []string{"apple", "apricot"}

    chan1 := make(chan string)
    chan2 := make(chan string)
    chan3 := make(chan string)

    var wg sync.WaitGroup

    go Pipe1(chan2, chan1, &wg)
    go Pipe2(chan3, chan2, &wg)
    go Pipe3(chan3, &wg)

    func (data []string) {
        defer wg.Done()
        for _, s := range data {
            wg.Add(1)
            chan1 <- s
        }
        go func() {
            wg.Wait()
            close(chan1)
        }()
    }(a1)
}

func Pipe1(out chan<- string, in <-chan string, wg *sync.WaitGroup) {
    defer wg.Done()
    for s := range in {
        wg.Add(1)
        out <- s + "s are"
    }
}
func Pipe2(out chan<- string, in <-chan string, wg *sync.WaitGroup) {
    defer wg.Done()
    for s := range in {
        wg.Add(1)
        out <- s + " good for you"
    }
}
func Pipe3(in <-chan string, wg *sync.WaitGroup) {
    defer wg.Done()
    for s := range in {
        wg.Add(1)
        fmt.Println(s)
    }
}


我的预期输出是:

apples are good for you
apricots are good for you

运行main 的结果不一致。有时我会得到两条线。有时我只是得到苹果。有时什么也没有输出。

您在 goroutine 中调用 wg.Wait,因此 main 可以在其他例程完成之前 return (因此您的程序退出)。这会导致您看到的行为,但仅取消 goroutine 是不够的。

您也普遍滥用了 WaitGroup;您的 AddDone 呼叫彼此不相关,并且您的 Done 数量没有 Add 数量多,因此 WaitGroup 永远不会完成。如果您在循环中调用 Add,那么每个循环迭代也必须导致 Done 调用;正如您现在所拥有的那样,您在每个循环之前 defer wg.Done(),然后在循环内调用 Add,导致一个 Done 和多个 Add。此代码需要进行重大修改才能按预期工作。

正如 Adrian 已经指出的,您的 WaitGroup.Add 和 WaitGroup.Done 调用不匹配。但是,在这种情况下,"I am done" 信号通常是通过关闭输出通道给出的。只有在多个 goroutine 之间共享工作时才需要 WaitGroups(即多个 goroutine 使用相同的通道),这里不是这种情况。

package main

import (
    "fmt"
)

func main() {
    a1 := []string{"apple", "apricot"}

    chan1 := make(chan string)
    chan2 := make(chan string)
    chan3 := make(chan string)

    go func() {
        for _, s := range a1 {
            chan1 <- s
        }

        close(chan1)
    }()

    go Pipe1(chan2, chan1)
    go Pipe2(chan3, chan2)

    // This range loop terminates when chan3 is closed, which Pipe2 does after
    // chan2 is closed, which Pipe1 does after chan1 is closed, which the
    // anonymous goroutine above does after it sent all values.
    for s := range chan3 {
        fmt.Println(s)
    }
}

func Pipe1(out chan<- string, in <-chan string) {
    for s := range in {
        out <- s + "s are"
    }

    close(out) // let caller know that we're done
}

func Pipe2(out chan<- string, in <-chan string) {
    for s := range in {
        out <- s + " good for you"
    }

    close(out) // let caller know that we're done
}

在操场上试试:https://play.golang.org/p/d2J4APjs_lL