开发者

How to stop a goroutine

开发者 https://www.devze.com 2023-03-22 08:39 出处:网络
I have a goroutine that calls a method, and passes returned value on a chann开发者_运维知识库el:

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.

  1. channel

  2. 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")

}
 

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号