为什么我的“完成”频道会随机关闭?

时间:2019-12-11 11:52:48

标签: go channel

我构建了以下go代码。

这个想法是建立一个完成的通道和一个生成int通道的生成器。 将它们链接到2级管道中 chanNumbers:= pipeb(done,pipea(done,gen(done)))

几秒钟后,取消完成的频道。 我希望看到生成器和管道的两个阶段取消并返回,但是“ PipeX正在终止”文本只是随机出现,我真的不明白为什么。 会有想法吗?

package main
import (
    "fmt"
    "time"
)

func gen(done <-chan interface{}) <-chan int {
    ret := make(chan int)
    cx := 0

    go func() {
        for {
            select {
            case <-done:
                fmt.Println("**Generator Terminates now")
                time.Sleep(2 * time.Second)
                fmt.Println("**Generator has terminated now")
                close(ret)
                return
            case ret <- cx:
                fmt.Printf("Gen : we push %d \n", cx)
                cx = cx + 1
            }
        }
    }()
    fmt.Println("Generator has created and returned its channel")
    return ret
}

func pipea(done <-chan interface{}, in <-chan int) <-chan int {
    ret := make(chan int)

    go func() {
        for {
            select {
            case <-done:
                fmt.Println("**pipeA terminates")
                time.Sleep(2 * time.Second)
                fmt.Println("**pipeA has terminated now")
                close(ret)
                return
            case tmp, ok := (<-in):
                if ok {
                    fmt.Printf("pipeA : we push %d \n", tmp)
                    ret <- tmp
                } else {
                    in = nil
                }
            }
        }
    }()
    return ret
}

func pipeb(done <-chan interface{}, in <-chan int) <-chan int {
    ret := make(chan int)

    go func() {
        for {
            select {
            case <-done:
                fmt.Println("**pipeB terminates")
                time.Sleep(2 * time.Second)
                fmt.Println("**pipeB has terminated now")
                close(ret)
                return
            case tmp, ok := (<-in):
                if ok {
                    fmt.Printf("pipeB : we push %d \n", tmp)
                    ret <- tmp
                } else {
                    in = nil
                }
            }
        }
    }()
    return ret
}

func main() {
    done := make(chan interface{})

    chanNumbers := pipeb(done, pipea(done, gen(done)))
    go func() {
        time.Sleep(2 * time.Second)
        close(done)
    }()

forloop:
    for {
        select {
        case n := <-chanNumbers:
            fmt.Printf("Received in main element : %d\n", n)
        case <-done:
            break forloop
        }
    }

    //end of the main program
    fmt.Println("Sleeping some seconds before termiating")
    time.Sleep(8 * time.Second)
    fmt.Println("exit...")
}

1 个答案:

答案 0 :(得分:2)

您有四个正在运行的例程:

  1. gen,您的生成器,写入无缓冲的输出通道,直到done
  2. pipeA,从gen读取,写入无缓冲的输出通道,直到done
  3. pipeB,从pipeA读取,写入无缓冲的输出通道,直到done
  4. main,从pipeB读到done

现在,当您关闭done时,它完全取决于go例程看到该命令的顺序。

如果main是第一个看到done已关闭的人,它将打破for循环并停止从pipeB消费。但是,如果pipeB仍在尝试写入输出通道(ret <- tmp),它将在此处阻塞;因此它永远不会到达<- done部分。

有两种方法可以解决此问题:

  1. 仅在您的生成器中收听done,并让其他例程使用for n := range in { }
  2. 也将发送逻辑放入select中,以便生成器和管道可以检测到done关闭的时间。

或者,您可能想使用缓冲的输出通道,但是即使那样,仍然可能出现此问题。