如何停止goroutine


102

我有一个goroutine,它调用一个方法,并在通道上传递返回的值:

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

如何停止这种goroutine?


1
根据您的情况,另一个答案是使用Go Context。我没有时间或知识来回答这个问题。我只想在这里提及它,以便搜索并找到不满意答案的人还有另一个线索可以拉(双关语意)。在大多数情况下,您应该按照公认的答案进行操作。这个答案提到了上下文:stackoverflow.com/a/47302930/167958
Omnifarious

Answers:


50

编辑: 我意识到您的问题是关于将值发送到goroutine中的chan之前,我匆忙写下了这个答案。下面的方法可以与上面建议的其他chan一起使用,或者利用您已经拥有的chan双向的事实,您可以只使用一个...

如果您的goroutine仅用于处理来自chan的项目,则可以使用内置的“ close”和特殊的通道接收表单。

就是说,一旦您完成了chan上的项目发送,就将其关闭。然后在您的goroutine内,您会为接收操作符获得一个额外的参数,该参数显示通道是否已关闭。

这是一个完整的示例(waitgroup用于确保过程继续进行,直到goroutine完成):

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()
}

15
内部goroutine的主体更常见地使用defercall来编写wg.Done(),并使用range ch循环遍历所有值直到关闭通道。
艾伦·多诺万

115

通常,您向goroutine传递(可能是单独的)信号通道。当您希望goroutine停止时,该信号通道用于将值推入。goroutine定期轮询该频道。一旦检测到信号,便退出。

quit := make(chan bool)
go func() {
    for {
        select {
        case <- quit:
            return
        default:
            // Do other stuff
        }
    }
}()

// Do stuff

// Quit goroutine
quit <- true

26
还不够好。如果goroutine由于错误而陷入无限循环该怎么办?
Elazar Leibovich,

232
然后应该修复该错误。
2011年

13
Elazar,您的建议是调用函数后停止该函数的方法。goroutine不是线程。它可以在不同的线程中运行,也可以在与您的线程相同的线程中运行。我知道没有一种语言支持您认为Go应该支持的内容。
杰里米·沃尔

5
@jeremy不反对Go,但是Erlang允许您终止正在运行循环功能的进程。
MatthewToday 2011年

10
多任务处理是合作的,不是抢先的。循环中的goroutine永远不会进入调度程序,因此永远不会被杀死。
杰夫·艾伦

34

您不能从外面杀死goroutine。您可以发出信号告知goroutine停止使用通道,但是goroutine上没有句柄可以进行任何类型的元管理。Goroutine旨在合作解决问题,因此杀死行为不端的人几乎永远不会是一个适当的回应。如果要隔离以提高鲁棒性,则可能需要一个过程。


您可能希望研究encoding / gob程序包,该程序包可使两个Go程序轻松通过管道交换数据结构。
杰夫·艾伦

就我而言,我有一个goroutine,它将在系统调用中被阻止,我需要告诉它中止系统调用,然后退出。如果我被禁止阅读某个频道,则可以按照您的建议进行操作。
全天

我以前看过那个问题。我们“解决”的方法是增加应用程序启动中的线程数,以匹配可能增加CPU数量的goroutine数量
rouzier

18

通常,您可以在goroutine中创建一个通道并接收停止信号。

在此示例中,有两种创建频道的方法。

  1. 渠道

  2. 情境。在示例中,我将演示context.WithCancel

第一个演示,使用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")
}

第二个演示,使用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")
}

11

我知道这个答案已经被接受了,但我想我会把2cents丢进去。我喜欢使用包。从根本上说,这是一个停止的退出通道,但是它做得很好,例如还传递了任何错误。受控制的例程仍然负责检查远程终止信号。Afaik不可能获取goroutine的“ id”并在行为不当(即陷入无限循环)时将其杀死。

这是我测试过的一个简单示例:

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

输出应如下所示:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above

这个包很有趣!您是否已测试过tombgoroutine的功能,以防万一内部发生某些事情而引起恐慌?从技术上讲,goroutine在这种情况下会退出,因此我假设它仍将称为延迟proc.Tomb.Done()...
Gwyneth Llewelyn

1
嗨,格温妮丝,是的,它proc.Tomb.Done()会在恐慌使程序崩溃之前执行,但是到底是什么呢?主goroutine可能有很小的机会执行某些语句,但无法从另一goroutine的恐慌中恢复,因此程序仍然崩溃。文档说:“当函数F调用紧急情况时,F的执行停止,F中的所有延迟函数都将正常执行,然后F返回其调用方。该过程将继续执行堆栈,直到返回当前goroutine中的所有函数为止,这时程序崩溃了。”
凯文·坎特韦尔

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.