154

I have a goroutine that calls a method, and passes returned value on a channel:

ch := make(chan int, 100)
go func(){
    for {
        ch <- do_stuff()
    }
}()

How do I stop such a goroutine?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Łukasz Gruner
  • 2,929
  • 3
  • 26
  • 28
  • 1
    Another answer, depending on your situation, is to use a Go Context. I don't have the time or the knowledge to create an answer about this. I just wanted to mention it here so people who search and find this answer unsatisfying have another thread to pull (pun intended). In most cases, you should do as the accepted answer suggests. This answer mentions contexts: https://stackoverflow.com/a/47302930/167958 – Omnifarious Feb 06 '18 at 20:59

8 Answers8

162

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
jimt
  • 25,324
  • 8
  • 70
  • 60
  • 42
    Not good enough. What if the goroutine is stuck in an endless loop, due to a bug? – Elazar Leibovich Jul 26 '11 at 11:39
  • 357
    Then the bug should be fixed. – jimt Jul 26 '11 at 20:51
  • 16
    Elazar, What you suggest is a way to stop a function after you've called it. A goroutine is not a thread. It may run in a different thread or it may run in the same thread as yours. I know of no language that supports what you seem to think Go should support. – Jeremy Wall Jul 30 '11 at 05:23
  • 5
    @jeremy Not disagreeing for Go, but Erlang allows you to kill a process that is running a looping function. – MatthewToday Jul 31 '11 at 01:36
  • 12
    Go multitasking is cooperative, not preemptive. A goroutine in a loop never enters the scheduler, so it can never be killed. – Jeff Allen Aug 28 '12 at 14:42
  • 4
    You'd be better closing the channel, this way any listener (not only the first to receive from `quit`) would receive the `quit` signal. – AntoineG May 20 '14 at 06:12
  • @Jeff This was correct when you wrote it I believe; Go used to schedule when certain blocking calls were made. Now, Go will potentially schedule during function calls(it's actually very interesting how this is implemented and maintains performance). So if the loop makes function calls it could invoke the scheduler. If not, it won't :) – Greg May 27 '14 at 23:25
  • 3
    If you decide to close the channel there's at least one difference: You might need to give the goroutine time to finish its "Do other stuff" before main() ends. I created a playground that demonstrates this: https://play.golang.org/p/EdL3fdxoBp – Donn Lee May 05 '16 at 17:21
  • 20
    @jimt I wish I could downvote your comment (although, +1 for the useful answer). What Elazar has said has some merit in it, though it might be wrongly worded: how can we stop a goroutine that is busy, that is the question. Why shake off someone when it could be explained perfectly with respect to the go's concurrency model, which Jeremy's answer made perfectly clear to me. There is no need for "charismatic" answers when the other person asks for an explanatory one. – Bora M. Alper Jul 19 '17 at 09:25
  • @BoraM.Alper and Jeremy, I have to be with Elazar because Thread.Kill() or now supported CancellationToken() in C# is what exactly for situation Elazar questioned. I wish there was a way when never ending accidental bug occurrs, then send down signal to forcekill and collect garbage. – swcraft Jun 08 '19 at 17:02
  • What about a long running task or non-loop based goroutine? – S.M.Mousavi Jun 03 '21 at 13:50
  • @DonnLee change time inside goroutine to 120 seconds and one in main function to 1 seconds. You let goroutine to finish loop as long as needed! perhaps 300 thousands milliseconds. (imagine a long task like trace route) – S.M.Mousavi Jun 03 '21 at 13:59
  • 2
    I can't really believe that the comment of @jimt got so many upvotes. There are plenty of situations where this bug might occur in an external library, so I want to make sure the go routine running this external method can get stopped at some point and doesn't use futher resources. – NotX Mar 08 '22 at 18:28
66

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()
}
jub0bs
  • 60,866
  • 25
  • 183
  • 186
laslowh
  • 8,482
  • 5
  • 34
  • 45
  • 27
    The body of the inner goroutine is more idiomatically written using `defer` to call `wg.Done()`, and a `range ch` loop to iterate over all values until the channel is closed. – Alan Donovan Jul 16 '15 at 13:53
64

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")
}
wasmup
  • 14,541
  • 6
  • 42
  • 58
zouying
  • 1,337
  • 10
  • 7
  • 3
    This was exactly I was looking for ! – Amit Aug 16 '21 at 14:45
  • This makes sense using a context, as that would be the ideal way, rather than using boolean as channel type and checking that flag if its emitted – vijayakumarpsg587 Apr 23 '22 at 06:07
  • why to use struct{} as channel type? It has to do with the zero value of a struct being nil? I would find a bool more readable otherwise – tuxErrante May 03 '23 at 10:51
  • @tuxErrante `struct{}` value is 0 bytes, whereas `bool` values are 1 byte. So it is just [more efficient](https://stackoverflow.com/a/47544821/10679134) – Hofsiedge Aug 12 '23 at 16:29
49

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.

SteveMcQwark
  • 2,169
  • 16
  • 9
  • And you might want to look into the encoding/gob package, which would let two Go programs easily exchange data structures over a pipe. – Jeff Allen Aug 28 '12 at 14:45
  • 1
    In my case, I have a goroutine that will be blocked on a system call, and I need to tell it to abort the system call and then exit. If I were blocked on a channel read, it would be possible to do as you suggest. – Omnifarious Feb 06 '18 at 19:44
  • I saw that issue before. The way we "solved" it was to increase the number of threads in the start of the application to match the number of goroutines that could possibly + the number of CPUs – rouzier May 09 '18 at 13:54
  • This is the right answer and other answers proposes *shutdown* if possible in your case. Imagine situations like cancelling long processing non-loop routines... – S.M.Mousavi Aug 10 '22 at 08:11
14

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
Kevin Cantwell
  • 1,092
  • 14
  • 19
  • This package is quite interesting! Have you tested to see what `tomb` does with the goroutine in case something happens inside it that throws a panic, for instance? Technically speaking, the goroutine exits in this case, so I'm assuming it will still call the deferred `proc.Tomb.Done()`... – Gwyneth Llewelyn Aug 11 '17 at 17:51
  • 1
    Hi Gwyneth, yes `proc.Tomb.Done()` would execute before the panic crashes the program, but to what end? It's possible that the main goroutine may have a _very small_ window of opportunity to execute some statements, but it has no way of recovering from a panic in another goroutine, so the program still crashes. Docs say: "When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller..The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes." – Kevin Cantwell Sep 06 '17 at 19:05
  • This is a great answer - better than using context in [some cases](https://dave.cheney.net/2017/08/20/context-isnt-for-cancellation). The API is a bit different but similar usage here with [tomb.v2](https://gopkg.in/tomb.v2). – Anirudh Ramanathan Oct 24 '20 at 02:14
11

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.

Martin Tournoij
  • 26,737
  • 24
  • 105
  • 146
mikespook
  • 768
  • 4
  • 13
0

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.

Bruno Calza
  • 2,732
  • 2
  • 23
  • 25
0

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

}
 

Danie
  • 386
  • 3
  • 8