I have a function that run for minutes and I'm trying to find a way to stop it using a channel.
I think I can't do it like I do in the following code since I think the select
will only handle the stop
case after the default
is done.
package main
import (
"fmt"
"time"
)
func main() {
stop := make(chan int)
go func() {
for {
select {
case <-stop:
fmt.Println("return")
return
default:
fmt.Println("block")
time.Sleep(5 * time.Second) // simulate a long running function
fmt.Println("unblock")
}
}
}()
time.Sleep(1 * time.Second)
stop <- 1
}
I reckon you can't do it: goroutines in Go are, in a sense, cooperative: until a goroutine actively tries to somehow figure out if it should exit, there's no way to force it to do so.
I'd say it's a feature in fact because if you could forcibly reap a long-running goroutine, you would be unable to be sure it exited cleanly, that is, properly released all resources it had acquired.
So either live with this (say, if your process wants to exit, just wait on that goroutine to finish) or restructure it so that it periodically checks whether it is signaled to quit. Or even consider offloading the task it performs to an external process (but note that while it's safe to kill a process with regard to releasing the resources it acquired from the OS, it's not safe with regard to external data that process might have been updating — such as files).
I don't think you can end a goroutine but you can switch to a different one. You can timeout a function by wrapping it inside a goroutine that send data to a channel once complete. There then needs to be select waiting for the returned channel or the channel from a timeout.
package main
import (
"fmt"
"time"
)
func waitForMe(){
time.Sleep(time.Second*5)
}
func main(){
c1 := make(chan string, 1)
go func(){
waitForMe()
c1 <- "waitForMe is done"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second*2):
fmt.Println("timed out")
}
}
Note every time you call select
, time.After()
or have a blocking channel, the goroutines switches to next available goroutine if possible.
Here's what's happening with your program.
package main
import (
"fmt"
"time"
)
func main() {
stop := make(chan int)
go func() {
fmt.Println("Goroutine B before for loop")
for {
fmt.Println("Goroutine B inside for loop")
select {
case <-stop:
fmt.Println("return")
return
default:
fmt.Println("Goroutine B default case")
fmt.Println("block")
time.Sleep(5 * time.Second) // simulate a long running function
fmt.Println("unblock")
}
}
}()
fmt.Println("Goroutine A before time.Sleep()")
time.Sleep(1 * time.Second)
fmt.Println("Goroutine A after sleep")
stop <- 1
fmt.Println("Goroutine A after stop")
}
Goroutine A before time.Sleep()
Goroutine B before for loop
Goroutine B inside for loop
Goroutine B default case
block
Goroutine A after sleep
unblock
Goroutine B inside for loop
return
Goroutine A after stop
Useful link: https://gobyexample.com/timeouts
You can only do this if you somehow make your function interruptible, i.e. you can't stop a blocking call on it's own. If you're writing the blocking function yourself, you can usually fashion a select with multiple cases and channels.
Your example would look like
package main
import (
"fmt"
"time"
)
func main() {
stop := make(chan int)
go func() {
for {
fmt.Println("block")
select {
case <-time.After(5 * time.Second):
fmt.Println("unblock")
case <-stop:
fmt.Println("stopped")
return
}
}
}()
time.Sleep(2 * time.Second)
stop <- 1
// this is just to give the goroutine time to write "stopped" before we exit
select{}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With