I have a goroutine that calls a method, and passes returned value on a chann开发者_运维知识库el:
ch := make(chan int, 100)
go func(){
for {
ch <- do_stuff()
}
}()
How do I stop such a goroutine?
Typically, you pass the goroutine a (possibly separate) signal channel. That signal channel is used to push a value into when you want the goroutine to stop. The goroutine polls that channel regularly. As soon as it detects a signal, it quits.
quit := make(chan bool)
go func() {
for {
select {
case <- quit:
return
default:
// Do other stuff
}
}
}()
// Do stuff
// Quit goroutine
quit <- true
EDIT: I wrote this answer up in haste, before realizing that your question is about sending values to a chan inside a goroutine. The approach below can be used either with an additional chan as suggested above, or using the fact that the chan you have already is bi-directional, you can use just the one...
If your goroutine exists solely to process the items coming out of the chan, you can make use of the "close" builtin and the special receive form for channels.
That is, once you're done sending items on the chan, you close it. Then inside your goroutine you get an extra parameter to the receive operator that shows whether the channel has been closed.
Here is a complete example (the waitgroup is used to make sure that the process continues until the goroutine completes):
package main
import "sync"
func main() {
var wg sync.WaitGroup
wg.Add(1)
ch := make(chan int)
go func() {
for {
foo, ok := <- ch
if !ok {
println("done")
wg.Done()
return
}
println(foo)
}
}()
ch <- 1
ch <- 2
ch <- 3
close(ch)
wg.Wait()
}
Generally, you could create a channel and receive a stop signal in the goroutine.
There two way to create channel in this example.
channel
context. In the example I will demo
context.WithCancel
The first demo, use channel
:
package main
import "fmt"
import "time"
func do_stuff() int {
return 1
}
func main() {
ch := make(chan int, 100)
done := make(chan struct{})
go func() {
for {
select {
case ch <- do_stuff():
case <-done:
close(ch)
return
}
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
time.Sleep(3 * time.Second)
done <- struct{}{}
}()
for i := range ch {
fmt.Println("receive value: ", i)
}
fmt.Println("finish")
}
The second demo, use context
:
package main
import (
"context"
"fmt"
"time"
)
func main() {
forever := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done(): // if cancel() execute
forever <- struct{}{}
return
default:
fmt.Println("for loop")
}
time.Sleep(500 * time.Millisecond)
}
}(ctx)
go func() {
time.Sleep(3 * time.Second)
cancel()
}()
<-forever
fmt.Println("finish")
}
You can't kill a goroutine from outside. You can signal a goroutine to stop using a channel, but there's no handle on goroutines to do any sort of meta management. Goroutines are intended to cooperatively solve problems, so killing one that is misbehaving would almost never be an adequate response. If you want isolation for robustness, you probably want a process.
I know this answer has already been accepted, but I thought I'd throw my 2cents in. I like to use the tomb package. It's basically a suped up quit channel, but it does nice things like pass back any errors as well. The routine under control still has the responsibility of checking for remote kill signals. Afaik it's not possible to get an "id" of a goroutine and kill it if it's misbehaving (ie: stuck in an infinite loop).
Here's a simple example which I tested:
package main
import (
"launchpad.net/tomb"
"time"
"fmt"
)
type Proc struct {
Tomb tomb.Tomb
}
func (proc *Proc) Exec() {
defer proc.Tomb.Done() // Must call only once
for {
select {
case <-proc.Tomb.Dying():
return
default:
time.Sleep(300 * time.Millisecond)
fmt.Println("Loop the loop")
}
}
}
func main() {
proc := &Proc{}
go proc.Exec()
time.Sleep(1 * time.Second)
proc.Tomb.Kill(fmt.Errorf("Death from above"))
err := proc.Tomb.Wait() // Will return the error that killed the proc
fmt.Println(err)
}
The output should look like:
# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
Personally, I'd like to use range on a channel in a goroutine:
https://play.golang.org/p/qt48vvDu8cd
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
c := make(chan bool)
wg.Add(1)
go func() {
defer wg.Done()
for b := range c {
fmt.Printf("Hello %t\n", b)
}
}()
c <- true
c <- true
close(c)
wg.Wait()
}
Dave has written a great post about this: http://dave.cheney.net/2013/04/30/curious-channels.
I am going to offer a slightly different approach than the ones provided here.
I am going to assume the goroutine
that needs to be stopped is performing some work that is not related at all to other goroutines
. That work will be represented by the default select case
:
default:
fmt.Println("working")
time.Sleep(1 * time.Second)
Another goroutine
(in my example will be the main
) decides that it should stop the goroutine
that is performing some work. You cannot really kill the goroutine
. Even if you could it would be a bad idea because it could leave the goroutine
in an undesired state. So, we have to use a channel to communicate that someone is signaling to the goroutine
to stop.
stop := make(chan struct{})
Since the goroutine
will be continuously performing some work. We will use a loop to represent that. And when the stop signal is sent, the goroutine
breaks out of the loop.
go func() {
L:
for {
select {
case <-stop:
fmt.Println("stopping")
break L
default:
fmt.Println("working")
time.Sleep(1 * time.Second)
}
}
}()
We can use another channel to indicate to the main
that the goroutine has stopped. Here's the full example:
package main
import (
"fmt"
"time"
)
func main() {
stop := make(chan struct{})
stopped := make(chan struct{})
go func() {
L:
for {
select {
case <-stop:
fmt.Println("stopping")
break L
default:
fmt.Println("working")
time.Sleep(1 * time.Second)
}
}
fmt.Println("stopped")
stopped <- struct{}{}
}()
<-time.After(5 * time.Second)
stop <- struct{}{} // send a signal to stop
close(stop)
<-stopped // wait for stop
}
The main
thread spawns a goroutine
to perform some work for some time (in this case 5 seconds). When the time expires, it sends a stop signal to the goroutine
and waits for it until the goroutine
is fully stopped.
I do the following, using close(quitCh). Using close() it will broadcast to all listening channels to exit, in this case, all the go routines are listening for the quit ch
.
package main
import (
"fmt"
"sync"
"time"
)
func routine(ch chan struct{}, wg *sync.WaitGroup, id int) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ch:
wg.Done()
fmt.Println(id, "quiting")
return
case <-ticker.C:
fmt.Println(id, "do your stuff")
}
}
}
func main() {
var wg sync.WaitGroup
c := make(chan struct{}, 1)
for i := 0; i < 3; i++ {
wg.Add(1)
go routine(c, &wg, i)
}
<-time.After(time.Second * 2)
close(c)
fmt.Println("waiting")
wg.Wait()
fmt.Println("Done")
}
精彩评论