4

Staring a goroutine which runs a recursive function, I want to send a signal to stop those recursive functions. This is the function (the functionality is not important):

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
}

This function may take a long time to be done and I want stop it after sending a quit signal and use the result (whatever it is). To run it:

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)
}

To stop the goroutine, I'm using a channel say quit and after closing it, the program works well, however I don't want really close the channel and I want just send a signal quit <- true. However, quit <- true doesn't work and I probably quits only one instance of recursion.

How can I stop all instances of recursive function by sending a quit signal?

masoud
  • 55,379
  • 16
  • 141
  • 208
  • In Go we have context.Context to create hierarchy of dependent goroutines; see http://stackoverflow.com/questions/42516717/how-to-stop-goroutine/42518866#42518866 – Kaveh Shahbazian Mar 05 '17 at 10:20
  • Why don't you want to close the channel? It is the simplest way to what you describe, so it would be good to know what other restrictions you have. – djd Mar 05 '17 at 10:34
  • If you use a single channel, the you are stopping all goroutines in your app. Or if you want use channels for this specific purpose, you have to manage them all manually. And if you just need to stop a portion of active goroutines in your app, how would you do that? Besides, using `context.Context` is an idiomatic pattern for managing goroutines in Go. – Kaveh Shahbazian Mar 05 '17 at 10:40
  • @djd: Imagine the caller side is in a loop, after each iteration I need to reuse the channel, how can I reopen the channel? – masoud Mar 05 '17 at 10:42
  • @deepmax: just make a fresh channel for each loop. It's an inexpensive operation, and you need to do something similar. Basically, with this approach, each unique channel represents a set of goroutines/function calls that will be cancelled at once. – djd Mar 06 '17 at 00:51
  • @djd: It would be nice if you write an answer using your approach and show me how it works. – masoud Mar 06 '17 at 09:39

4 Answers4

6

You can do the what you are going to do using context.

You can pass a context.Context object as the first parameter to the function which you need to stop from outside, and call the corresponding cancel function to send a "cancellation signal" to the function, which will cause the Done() channel of the context.Context to be closed, and the called function will thus be notified of the cancellation signal in a select statement.

Here is how the function handles the cancellation signal using 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
}

And here is how you can call the function with the new signature:

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)
}
  • First you have to check for `<-ctx.Done()` not only at the beginning of `RecursiveFunc` function, but also inside the `for` loop. And second, in your `main` function you have to call `wg.Add(1)` before lunching the goroutine so there will be no need for the crafted delay (`time.Sleep(10 * time.Millisecond)`) and also if `RecursiveFunc` takes less than 10ms, your goroutine will never get lunched and `wg.Wait()` will return immediately. – Kaveh Shahbazian Mar 05 '17 at 10:33
  • @KavehShahbazian: I agree with your second point, but what's wrong to let `RecursiveFunc` inside the `for` be executed and exits after `ctx.Done`? – masoud Mar 05 '17 at 10:40
  • It just keeps lunching new goroutines even if the context got cancelled at that point, and it shouldn't. – Kaveh Shahbazian Mar 05 '17 at 10:42
  • and after launching, new goroutines will be exited immediately, are you pointing to an optimization or my approach will cause problems? – masoud Mar 05 '17 at 10:45
  • @KavehShahbazian I agree but I guess that's not the question here and I'll just let the poster figure out and implement the functionality that he's looking for. – Mohammad Nasirifar Mar 05 '17 at 10:48
  • @deepmax It depends! :) In your case, cancelling things, would make `total` an invalid, meaningless, value (because it got interrupted and it's meaning is not actually "total" anymore). So, yes you are right, they will exit immediately and it's an optimization. But as I've just described, it's also conceptually correct. – Kaveh Shahbazian Mar 05 '17 at 10:49
1

I was in a similar situation recently and like your case, the quit signal was consumed by one of the recursion branch leaving the other branches without a signal. I solved this by forwarding the stop signal to the channel before returning from the function.

For example, you can modify the select inside the recursive function to be:

if quit != nil {
    select {
    case <-quit:
        quit <- true // forward the signal
        return 0
    default:
    }
}
nullgraph
  • 297
  • 1
  • 6
  • 17
0

function recursion loop infinitely util the condition >= 10 matchs, don't forget to close channel and return

func main() {
    x := 1
    xChan := make(chan int)
    go recursion(x, xChan)
    select {
    case result := <-xChan:
        log.Println("get chan result :", result)
        break
    }
}

func recursion(i int, xChan chan int) {
    if i >= 10 {
        xChan <- i
        close(xChan)
        return
    }
    a := i + i
    log.Println("a :", a)
    recursion(a, xChan)
}
-1

Try to add flag to continue execution, but it can be not thread safe.

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
vodolaz095
  • 6,680
  • 4
  • 27
  • 42