关闭完成通道后 goroutine 缺少打印

当我注意到并非管道中的所有关闭打印都被打印时,我正在根据“Go 中的并发”一书中的示例运行以下代码。

看到“完成倍增!” 不见了。

另一方面,NumGoroutine() 只显示 main 函数正在运行。

以下代码有什么问题?(https://play.golang.org/p/tkFgvKboVgS)


package main


import (

    "fmt"

    "runtime"

    "time"

)


func main() {

    generator := func(done <-chan struct{}) <-chan int {

        intStream := make(chan int)

        i:=0

        go func() {

            defer close(intStream)

            for {

                select {

                case <-done:

                    fmt.Println("done generator!")

                    return

                case intStream <- i:

                    time.Sleep(1 * time.Second)

                    i++

                }

                fmt.Println("generator after select")

            }

        }()

        return intStream

    }


    multiply := func(

        done <-chan struct{},

        intStream <-chan int,

        multiplier int,

    ) <-chan int {

        multipliedStream := make(chan int)

        go func() {

            defer close(multipliedStream)

            for i := range intStream {

                select {

                case <-done:

                    fmt.Println("done multiply !")

                    return

                case multipliedStream <- i * multiplier:

                }

                fmt.Println("multiply after select")

            }

        }()

        return multipliedStream

    }

    add := func(

        done <-chan struct{},

        intStream <-chan int,

        additive int,

    ) <-chan int {

        addedStream := make(chan int)

        go func() {

            defer close(addedStream)

            for i := range intStream {

                select {

                case <-done:

                    fmt.Println("done add !")

                    return

                case addedStream <- i + additive:

                }

                fmt.Println("add after select")

            }

        }()

        return addedStream

    }


慕工程0101907
浏览 198回答 2
2回答

隔江千里

有些代码路径不会打印某些done消息。调度程序碰巧选择了一个不为multiply. 如果您稍微更改代码(例如,在与现在不同的实例上登录),您会发现它也可能会丢失add done消息。(https://play.golang.org/p/meEPM5GR9Rr)。原因如下:如果done消息在生成器将数字写入通道并且乘法器读取它之后立即到达,那么乘法器会看到done可用并选择它。multiplier打印done消息时就是这种情况。如果done消息在 multiplier 在 for 循环中等待时到达,则 multiplier 将接收输入通道(而不是done通道)的关闭,导致 for 循环终止而不打印done消息。出现问题是因为您正在从 for 循环中的通道读取,然后进行选择。在等待 for 循环从通道中读取数据时,不会评估与选择相关的任何事件。解决这个问题的更好方法是不使用 for 循环从通道中读取。例如:for {&nbsp; &nbsp; &nbsp;select {&nbsp; &nbsp; &nbsp; &nbsp; case <-done:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return&nbsp; &nbsp; &nbsp; &nbsp; case i, ok:= <-intstream:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if !ok {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case <- done:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case addedStream <- i + additive:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}&nbsp; &nbsp; &nbsp;}}

拉丁的传说

你的add和multiply例程不是永远的循环,而是for ... range循环。因此,在每个循环的顶部,它们等待下一个整数,而不是等待select接收关闭done或将结果发送到它们的流。这不是问题,但这意味着如果它们的输入流关闭,它们将返回而不进入循环本身。如果我添加fmt.Println调用以暴露由于到达输入流的末尾而退出的点,则行为会略有变化(可能是由于时间的原因;我没有费心去解释它,而Burak Serdar已经发布了他的答案我正在输入这个),输出变为:add after select2multiply after selectgenerator after selectmultiply after selectadd after select4generator after selectmultiply after selectadd after select6generator after selectClosed donedone multiply !add got end of stream - done!finished iterating pipelinegenerator after selectdone generator!ramaining goroutines: 1finished!通常更合理的是只让生成器本身接收done信号,并使流水线函数始终写入所有结果,这使得它们更可预测。当然,读取每个管道的人必须读到最后——但你已经在主 goroutine 中这样做了,所以我们只是在整个过程中传播它。 这是以这种方式执行的代码的简化版本;它输出:2generator after select4generator after select6generator after selectClosed done8generator after selectdone generator!multiply got end of stream - done!add got end of stream - done!finished iterating pipelineremaining goroutines: 1请注意,这一次,我们从最终生成的值 (3) 中得到最终的计算值 (8)。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go