简体   繁体   中英

How can I stop the goroutine based on the returned value from that goroutine

Like here I created a go playground sample: sGgxEh40ev , but cannot get it work.

quit := make(chan bool)
res := make(chan int)

go func() {
    idx := 0
    for {
        select {
        case <-quit:
            fmt.Println("Detected quit signal!")
            return
        default:
            fmt.Println("goroutine is doing stuff..")
            res <- idx
            idx++
        }
    }

}()

for r := range res {
    if r == 6 {
        quit <- true
    }
    fmt.Println("I received: ", r)
}

Output:

goroutine is doing stuff..
goroutine is doing stuff..
I received:  0
I received:  1
goroutine is doing stuff..
goroutine is doing stuff..
I received:  2
I received:  3
goroutine is doing stuff..
goroutine is doing stuff..
I received:  4
I received:  5
goroutine is doing stuff..
goroutine is doing stuff..
fatal error: all goroutines are asleep - deadlock!

Is this possible? Where am I wrong

The problem is that in the goroutine you use a select to check if it should abort, but you use the default branch to do the work otherwise.

The default branch is executed if no communications (listed in case branches) can proceed. So in each iteration quit channel is checked, but if it cannot be received from (no need to quit yet), default branch is executed, which unconditionally tries to send a value on res . Now if the main goroutine is not ready to receive from it, this will be a deadlock. And this is exactly what happens when the sent value is 6 , because then the main goroutine tries to send a value on quit , but if the worker goroutine is in the default branch trying to send on res , then both goroutines try to send a value, and none is trying to receive ! Both channels are unbuffered, so this is a deadlock.

In the worker goroutine you must send the value on res using a proper case branch, and not in the default branch:

select {
case <-quit:
    fmt.Println("Detected quit signal!")
    return
case res <- idx:
    fmt.Println("goroutine is doing stuff..")
    idx++
}

And in the main goroutine you must break out from the for loop so the main goroutine can end and so the program can end as well:

if r == 6 {
    quit <- true
    break
}

Output this time (try it on the Go Playground ):

goroutine is doing stuff..
I received:  0
I received:  1
goroutine is doing stuff..
goroutine is doing stuff..
I received:  2
I received:  3
goroutine is doing stuff..
goroutine is doing stuff..
I received:  4
I received:  5
goroutine is doing stuff..
goroutine is doing stuff..

The fundamental issue is that producer must always check in between sending values if the consumer (main in your case) has decided to quit reading (in your code this is optional). What's happening is even before the value of quit is sent (and received), the producer goes ahead and sends the next value on res which the consumer never is able to read - the consumer is in fact trying to send the value on the quit channel expecting the producer to read. Added a debug statement which can help you understand : https://play.golang.org/p/mP_4VYrkZZ , - producer is trying to send 7 on res and blocking, and then consumer trying to send value on quit and blocking. Deadlock!

One possible solution is as follows (using a Waitgroup is optional, needed only if you need a clean exit from producer side before return):

package main

import (
    "fmt"
    "sync"
)

func main() {
    //WaitGroup is needed only if need a clean exit for producer
    //that is the producer should have exited before consumer (main)
    //exits - the code works even without the WaitGroup
    var wg sync.WaitGroup
    quit := make(chan bool)
    res := make(chan int)

    go func() {
        idx := 0
        for {

            fmt.Println("goroutine is doing stuff..", idx)
            res <- idx
            idx++
            if <-quit {
                fmt.Println("Producer quitting..")
                wg.Done()
                return
            }

            //select {
            //case <-quit:
            //fmt.Println("Detected quit signal!")
            //time.Sleep(1000 * time.Millisecond)
            //  return
            //default:
            //fmt.Println("goroutine is doing stuff..", idx)
            //res <- idx
            //idx++
            //}
        }


    }()
    wg.Add(1)
    for r := range res {
        if r == 6 {
            fmt.Println("Consumer exit condition met: ", r)
            quit <- true
            break
        }
        quit <- false
        fmt.Println("I received: ", r)
    }
    wg.Wait()

}

Output:

goroutine is doing stuff.. 0
I received:  0
goroutine is doing stuff.. 1
I received:  1
goroutine is doing stuff.. 2
I received:  2
goroutine is doing stuff.. 3
I received:  3
goroutine is doing stuff.. 4
I received:  4
goroutine is doing stuff.. 5
I received:  5
goroutine is doing stuff.. 6
Consumer exit condition met:  6
Producer quitting..

On playground : https://play.golang.org/p/N8WSPvnqqM

As @icza's answer is pretty clean, which @Ravi's goes to the synchronised way.

But coz I don't want to spend that much effort to restructure the code, and also I don't want to go to the synchronised way, so eventually went to the defer panic recover flow control, as below:

func test(ch chan<- int, data []byte) {
    defer func() {
        recover()
    }()
    defer close(ch)

    // do your logic as normal ...
    // send back your res as normal `ch <- res`
}

// Then in the caller goroutine

ch := make(chan int)
data := []byte{1, 2, 3}
go test(ch, data)

for res := range ch {
    // When you want to terminate the test goroutine:
    //     deliberately close the channel
    //
    // `go -race` will report potential race condition, but it is fine
    //
    // then test goroutine will be panic due to try sending on the closed channel,
    //     then recover, then quit, perfect :) 
    close(ch)
    break
}

any potential risk with this approach?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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