为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
当年话下 2023-08-14 17:00:02
当我注意到并非管道中的所有关闭打印都被打印时,我根据《Go 中的并发》一书中的示例运行以下代码。看到&ldquo;完成倍增!&rdquo; 不见了。另一方面,NumGoroutine() 显示仅主函数正在运行。下面的代码有什么问题?package mainimport (    "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    }
查看完整描述

2 回答

?
眼眸繁星

TA贡献1873条经验 获得超9个赞

有些代码路径不会打印某些消息done。调度程序碰巧选择了一个不打印 的那个multiply。如果您稍微更改代码(例如,在与现在不同的实例上登录),您会发现它也可能会错过该消息add done。原因如下:

如果done消息在生成器将数字写入通道并且乘法器读取它之后立即到达,则乘法器会看到该数字done可用并选择该数字。multiplier打印消息时就是这种情况done。如果当donemultiplier 在 for 循环中等待时消息到达,则 multiplier 将接收输入通道(而不是通道)上的关闭消息done,从而导致 for 循环终止而不打印done消息。

出现问题的原因是您正在 for 循环中读取通道,然后进行选择。在等待 for 循环从通道读取数据时,不会评估与 select 相关的任何事件。

处理此问题的更好方法是不使用 for 循环从通道读取。例如:

for {

     select {

        case <-done:

           return

        case i, ok:= <-intstream:

           if !ok {

              return

           }

           select {

               case <- done:

                    return

               case addedStream <- i + additive:

           }

     }

}


查看完整回答
反对 回复 2023-08-14
?
一只甜甜圈

TA贡献1836条经验 获得超5个赞

你的add例程multiply不是永远循环,而是for ... range循环。因此,在每个循环的顶部,它们等待下一个整数,而不是等待从其流select接收关闭done或将结果发送到其流。这不是问题,但这意味着如果它们的输入流关闭它们将返回而不进入循环本身。

如果我添加fmt.Println调用来公开它们由于到达输入流末尾而退出的点,则行为会略有变化(可能是由于时间原因;同时我正在输入这个),输出变成:

add after select

2

multiply after select

generator after select

multiply after select

add after select

4

generator after select

multiply after select

add after select

6

generator after select

Closed done

done multiply !

add got end of stream - done!

finished iterating pipeline

generator after select

done generator!

ramaining goroutines: 1

finished!

通常更合理的做法是仅让生成器本身接收done信号,并让管道函数始终写入其所有结果,这使它们更可预测。当然,无论谁正在读取每个管道,都必须读到最后——但是您已经在主 goroutine 中执行了此操作,因此我们只是将其传播到整个管道。 这是您的代码的简化版本,以这种方式执行此操作;它输出:


2

generator after select

4

generator after select

6

generator after select

Closed done

8

generator after select

done generator!

multiply got end of stream - done!

add got end of stream - done!

finished iterating pipeline

remaining goroutines: 1

请注意,这一次,我们从最终生成值 (3) 中得到最终计算值 (8)。


查看完整回答
反对 回复 2023-08-14
  • 2 回答
  • 0 关注
  • 227 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号