【问题标题】:Stop all recursive functions in a goroutine停止 goroutine 中的所有递归函数
【发布时间】:2021-03-03 01:02:20
【问题描述】:

盯着一个运行递归函数的 goroutine,我想发送一个信号来停止这些递归函数。这是功能(功能不重要):

func RecursiveFunc(x int, depth int, quit chan bool) int {

    if depth == 0 {
        return 1
    }

    if quit != nil {
        select {
        case <-quit:
            return 0
        default:
        }
    }

    total := 0

    for i := 0; i < x; i++ {

        y := RecursiveFunc(x, depth - 1, quit)

        if y > 0 {
            total += y
        }

    }

    return total
}

这个函数可能需要很长时间才能完成,我想在发送退出信号后停止它并使用结果(不管它是什么)。运行它:

import (
    "fmt"
    "time"
    "sync"
)

func main() {

    quit := make(chan bool)
    wg := &sync.WaitGroup{}
    result := -1

    go func() {
        defer wg.Done()
        wg.Add(1)
        result = RecursiveFunc(5, 20, quit)
    }()

    time.Sleep(10 * time.Millisecond)

    close(quit) // Using `quit <- true` doesn't work

    wg.Wait()

    fmt.Println(result)
}

为了停止 goroutine,我使用了一个通道,比如 quit,关闭它后,程序运行良好,但是我不想真正关闭通道,我只想发送一个信号 quit &lt;- true。但是,quit &lt;- true 不起作用,我可能只退出了一个递归实例。

如何通过发送退出信号来停止递归函数的所有实例?

【问题讨论】:

  • 在 Go 中,我们有 context.Context 来创建依赖 goroutine 的层次结构;见stackoverflow.com/questions/42516717/how-to-stop-goroutine/…
  • 为什么不想关闭频道?这是您描述的最简单的方法,因此最好知道您还有哪些其他限制。
  • 如果您使用单个通道,您将停止应用程序中的所有 goroutine。或者,如果您想为此特定目的使用频道,则必须手动管理它们。如果你只需要在你的应用程序中停止一部分活动的 goroutines,你会怎么做?此外,使用 context.Context 是 Go 中管理 goroutine 的惯用模式。
  • @djd:想象一下调用方处于一个循环中,每次迭代后我需要重用频道,我该如何重新打开频道?
  • @deepmax:只需为每个循环创建一个新频道。这是一个廉价的操作,你需要做类似的事情。基本上,使用这种方法,每个唯一通道代表一组将立即取消的 goroutines/函数调用。

标签: go goroutine


【解决方案1】:

您可以使用context 执行您将要执行的操作。

您可以将context.Context对象作为第一个参数传递给需要从外部停止的函数,并调用相应的cancel函数向函数发送“取消信号”,这将导致@ context.Context 的 987654324@ 通道将被关闭,因此将在 select 语句中将取消信号通知调用函数。

下面是函数如何使用context.Context处理取消信号:

func RecursiveFunc(ctx context.Context, x int, depth int) int {

    if depth == 0 {
        return 1
    }

    select {
    case <-ctx.Done():
        return 0
    default:
    }

    total := 0

    for i := 0; i < x; i++ {

        y := RecursiveFunc(ctx, x, depth-1)

        if y > 0 {
            total += y
        }

    }

    return total
}

以下是使用新签名调用函数的方法:

func main() {

    wg := &sync.WaitGroup{}
    result := -1

    ctx, cancel := context.WithCancel(context.Background())

    go func() {
        defer wg.Done()
        wg.Add(1)
        result = RecursiveFunc(ctx, 5, 20)
    }()

    time.Sleep(10 * time.Millisecond)

    cancel()

    wg.Wait()

    fmt.Println(result)
}

【讨论】:

  • 首先你必须检查&lt;-ctx.Done(),不仅在RecursiveFunc函数的开头,而且在for循环内部。其次,在你的 main 函数中,你必须在 goroutine 午餐前调用 wg.Add(1) 这样就不需要精心设计的延迟(time.Sleep(10 * time.Millisecond)),而且如果 RecursiveFunc 花费少于 10 毫秒,你的 goroutine 永远不会吃午饭,wg.Wait() 将立即返回。
  • @KavehShahbazian:我同意你的第二点,但是让RecursiveFuncfor 中执行并在ctx.Done 之后退出有什么问题?
  • 即使上下文在此时被取消,它也会继续提供新的 goroutines,它不应该。
  • 启动后,新的goroutines会立即退出,你是指优化还是我的方法会导致问题?
  • @KavehShahbazian 我同意,但我想这不是这里的问题,我会让发布者弄清楚并实现他正在寻找的功能。
【解决方案2】:

我最近遇到了类似的情况,就像你的情况一样,退出信号被一个递归分支消耗,而其他分支没有信号。我通过在从函数返回之前将停止信号转发到通道来解决这个问题。

比如可以将递归函数里面的select修改为:

if quit != nil {
    select {
    case <-quit:
        quit <- true // forward the signal
        return 0
    default:
    }
}

【讨论】:

    【解决方案3】:

    尝试添加标志以继续执行,但它可能不是线程安全的。

    var finishIt bool
    
    func RecursiveFunc(x int, depth int, quit chan bool) int {
       if finishIt {
        return 0
       }
    //other code here
    }
    
    
    //some code here, but than we decide to stop it
    finishIt = true
    

    【讨论】:

    • 我认为变量是全局的而不是为这个特定的模块导出是可以的。
    猜你喜欢
    • 1970-01-01
    • 2013-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多