【问题标题】:What is an elegant way to shut down a chain of goroutines linked by channels?关闭由通道链接的 goroutine 链的优雅方法是什么?
【发布时间】:2016-04-22 05:55:27
【问题描述】:

我是围棋学习者。为了更好地理解 channel 和 goroutines 的关心和馈送,我正在尝试构建一个 Sieve of Eratosthenes 作为一组 goroutines 通过 channel 连接到管道中。

这是我目前所拥有的:

// esieve implements a Sieve of Eratosthenes
// as a series of channels connected together
// by goroutines
package main

import "fmt"

func sieve(mine int, inch chan int) {
    start := true                        // First-number switch
    ouch := make(chan int)               // Output channel for this instance
    fmt.Printf("%v\n", mine)             // Print this instance's prime
    for next := <-inch; next > 0; next = <-inch {  // Read input channel
        fmt.Printf("%v <- %v\n",mine,next)         // (Trace)
        if (next % mine) > 0 {                     // Divisible by my prime?
            if start {                   // No; is it the first number through? 
                go sieve(next, ouch)     // First number - create instance for it
                start = false            // First time done
            } else {                     // Not first time
                ouch <- next             // Pass it to the next instance
            }
        }
    }
}

func main() {
    lim := 30                     // Let's do up to 30
    fmt.Printf("%v\n", 2)         // Treat 2 as a special case
    ouch := make(chan int)        // Create the first segment of the pipe
    go sieve(3, ouch)             // Create the instance for '3'
    for prime := 3; prime < lim; prime += 2 { // Generate 3, 5, ...
        fmt.Printf("Send %v\n", prime)        // Trace
        ouch <- prime                         // Send it down the pipe
    }
}

就它而言,它工作得很好。

但是,当我完成主循环时,main 会在仍处于 sieve 实例管道中的所有数字传播到末尾之前退出。

让主例程等待一组 goroutines(它只“知道”第一个)完成的最简单、最优雅或普遍接受的方法是什么?

【问题讨论】:

  • 最简单且推荐的解决方案是使用main 将阻塞的另一个通道并接收操作结果。
  • @icza :我部分同意;该问题涉及更简单的(星型)拓扑。在我的情况下,只有管道中的最后一个 goroutine 必须发出关闭信号,一旦它知道它是最后一个。我想我已经接近最终答案了,我最终会在这里发布。顺便说一句,使用令人愉快的构造!
  • @creker:谢谢。这有助于我了解我必须如何解决问题,所以如果你愿意发布作为答案,我会接受。我还计划将结果发布为我自己的答案。

标签: go channel goroutine


【解决方案1】:

@izca 解除我的僵局之后,在一切都完成后,在几次涉及死锁的错误启动之后,这是我的解决方案正常工作:

// esieve implements a Sieve of Eratosthenes
// as a series of channels connected together
// by goroutines
package main

import "fmt"

func sieve(mine int,                  // This instance's own prime
           inch chan int,             // Input channel from lower primes
           done chan int,             // Channel for signalling shutdown
           count int) {               // Number of primes - counter
    start := true                     // First-number switch
    ouch := make(chan int)            // Output channel, this instance
    fmt.Printf("%v ", mine)           // Print this instance's prime
    for next := <-inch; next > 0; next = <-inch { // Read input channel
        if (next % mine) > 0 {        // Divisible by my prime?
            if start {                // No; first time through?
                go sieve(next, ouch, done, count+1) // First number,
                                                    // create instance for it
                start = false         // First time done
            } else {                  // Not first time
                ouch <- next          // Pass to next instance
            }
        }
    }
    if start {                        // Just starting?
        close(done)                   // Yes - we're last in pipe - signal done
        print("\n",count," primes\n") // Number of primes/goroutines
    } else {
        close(ouch)                   // No - send the signal down the pipe
    }
}

func main() {
    lim := 100                        // Let's do up to 100
    done := make(chan int)            // Create the done return channel
    ouch := make(chan int)            // Create the first segment of the pipe
    go sieve(2, ouch, done, 1)        // Create the first instance for '2'
    for prime := 3; prime < lim; prime += 1 { // Generate odd numbers
        ouch <- prime                         // Send numbers down the pipe
    }
    close(ouch)                       // Send the done signal down the pipe
    <- done                           // and wait for it to come back
}

与许多其他语言相比,Go 在这种编程方面的优雅和简单给我留下了深刻的印象。当然,我自己声称的疣。

如果合适的话,我会欢迎批评的 cmets。

【讨论】:

  • 在 sieve() 中,您使用 ':=' 语法定义了一个本地 'start' 变量。这意味着您调用的每个 sieve goproc 都会认为它是起始 proc。
  • @maurice 抱歉,这个名字可能选错了。 start 变量的意图是它是每个 goroutine 的本地变量,并通过读取第一个素数来告诉它是否已完成启动,这使得它一直沿管道向下到达该 goroutine。我错了吗? (我一直不擅长选择变量名。)
  • 啊,我现在看到您允许每个 goproc 筛子仅在产生子项时才关闭子项输入。知道了。谢谢。
  • @maurice :是的,就是这样。所有产生下游实例的 «goproc sieve» 都会关闭它的通道;最后一个实例,它没有产生,只是关闭 «done» 通道以指示主要任务结束。
【解决方案2】:

关于你的标题问题,当你不再需要它们时杀死工人 goroutine: 你可以使用 Done 成语。从封闭通道读取会产生零值。

创建一个新频道done。当从这个通道读取成功时,goroutines 知道他们应该退出。获得所需的所有值后,关闭 main 中的通道。

检查您是否可以从频道done 读取,并通过返回退出,或者在可用时从下一个读取。这部分替换了 for 循环中对 next 的赋值:

select {
case <-done:
return
case next = <- inch:
}

在通道上进行测距也可以,因为关闭该通道会退出循环。

至于反过来,你的body问题,等待一组goroutines完成:

使用sync.WaitGroup

var wg sync.WaitGroup
wg.Add(goroutineCount)

当每个 goroutine 完成时:

wg.Done()

或者使用延迟:

defer wg.Done()

等待他们全部报告完成:

wg.Wait()

在您的示例中,只需在启动新的 goroutine 时调用 wg.Add(1),然后再调用 wg.Done() 并返回。只要您只达到零一次,wg.Wait() 就会按预期工作,所以wg.Add(1)wg.Done 之前。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-23
    • 1970-01-01
    • 2022-11-17
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多