繁体   English   中英

退出等待无缓冲通道的多个 go 例程

[英]exiting multiple go routines waiting on an unbuffered channel

我试图同时退出多个 Goroutines。 根据https://www.godesignpatterns.com/2014/04/exiting-multiple-goroutines-simultaneously.html有一个定义明确的方法。

我看到的另一种方法如下

package main

import (
    "fmt"
    "time"
)

func main() {
    var inCh chan int = make(chan int, 100)
    var exit chan bool = make(chan bool)

    for i := 0; i < 20; i++ {
        go func(instance int) {
            fmt.Println("In go routine ", instance)
            for {
                select {
                case <-exit:
                    fmt.Println("Exit received from ", instance)
                    exit <- true
                    return
                case value := <-inCh:
                    fmt.Println("Value=", value)
                }
            }
        }(i)
    }

    time.Sleep(1 * time.Second)

    exit <- true
    <-exit   // Final exit
    fmt.Println("Final exit")
}

但我很困惑,我真的不明白为什么最后的无缓冲通道作为最后一条语句执行。 实际上我有 20 个 go 例程监听退出通道。 一个人会随机收到它并将其发送给另一个人。 为什么go例程中的接收总是在发生,只有当所有例程都完成时,才会执行注释为“// Final Exit”的通道接收?

如果有人能给我一个解释,我将不胜感激。

如链接文章中所示,使用close()进行取消。

有问题的代码不能保证工作。 这是一个失败的场景:

  1. 一个 goroutine 已准备好从exit接收。 所有其他 goroutines 在其他地方忙。
  2. main 发送的值由 ready goroutine 接收。
  3. 该 goroutine 向exit发送一个值,该值由main()接收。

其他 goroutine 不会退出,因为没有更多的值被发送到exit 请参阅这个使用 time.Seep 来引发问题场景的游乐场示例

为什么go例程中的接收总是在发生,只有当所有例程都完成时,才会执行注释为“// Final Exit”的通道接收?

该程序的执行就像通道维护一个有序的等待 goroutines 队列一样,但规范中没有任何内容可以保证该行为。 即使通道有一个有序队列,如果 goroutine 正在做一些事情而不是等待从exit接收,程序也会遇到上面的场景。

如果你注意到你程序的 output

In go routine  6
In go routine  0
In go routine  7
.
.
Exit received from  6
Exit received from  0
Exit received from  7
.
.
Final exit

他们以与他们开始时相同(或几乎相同)的顺序被调用。 如果您的 Go 例程都不忙,将使用第一个注册的例程。 这只是运行时的一个实现,我不会指望这种行为。

您的最终退出是要收听的最后一个频道,因此最后使用。

如果你删除 time.Sleep 在你的循环之后你的最终退出将几乎立即被调用并且你的大部分 go 例程将不会收到退出信号

Output 没有时间。睡眠(将在运行之间)

In go routine  0
Exit received from  0
In go routine  1
In go routine  2
In go routine  3
In go routine  4
In go routine  5
In go routine  6
In go routine  7
In go routine  14
In go routine  15
In go routine  16
In go routine  17
In go routine  18
In go routine  19
Final exit

考虑这个轻微的修改。

package main

import (
    "fmt"
)

func main() {
    var exit chan int = make(chan int)
        var workers = 20
    for i := 0; i < workers; i++ {
        go func(instance int) {
            fmt.Println("In go routine ", instance)
            for {
                select {
                case i := <-exit:
                    fmt.Println("Exit", i, "received from ", instance)
                    exit <- i-1
                    return
                }
            }
        }(i)
    }
    exit <- workers
    fmt.Println("Final exit:", <-exit)
}

在这里,我做了三件事:首先,为简洁起见,我删除了未使用的频道。 其次,我删除了睡眠。 第三,我将exit通道更改为每次通过都会递减的int通道。 如果我传递工人的数量,“最终”消息中除0以外的任何值都表示工人被丢弃。

这是一个示例运行:

% go run t.go
In go routine  8
In go routine  5
In go routine  0
In go routine  2
Exit 20 received from  8
Exit 19 received from  5
Final exit: 18
In go routine  13

main调用time.Sleep时,直到睡眠结束才会安排它。 其他 goroutines 都有这个时间来设置他们的频道阅读器。 我只能假设,因为我在任何地方都找不到它,通道读者可能会大致按时间顺序排队 - 因此, sleep保证main的读者是最后一个。

如果这是一贯的行为,那肯定是不可靠的

请参阅多个 goroutines listening on one channel了解更多关于此的想法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM