如何停止一个 goroutine

Posted

技术标签:

【中文标题】如何停止一个 goroutine【英文标题】:How to stop a goroutine 【发布时间】:2011-10-12 01:31:57 【问题描述】:

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

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

如何停止这样的 goroutine?

【问题讨论】:

根据您的情况,另一个答案是使用 Go 上下文。我没有时间或知识来创建有关此的答案。我只是想在这里提一下,以便搜索并发现此答案不满意的人有另一个线程可以拉(双关语)。在大多数情况下,您应该按照公认答案的建议进行操作。这个答案提到了上下文:***.com/a/47302930/167958 【参考方案1】:

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

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

// Do stuff

// Quit goroutine
quit <- true

【讨论】:

不够好。如果 goroutine 由于 bug 而陷入死循环怎么办? 那么bug应该被修复了。 Elazar,您建议的是一种在您调用函数后停止该函数的方法。 goroutine 不是线程。它可能在不同的线程中运行,也可能在与您的线程相同的线程中运行。我知道没有一种语言支持你认为 Go 应该支持的东西。 Go 多任务处理是合作的,而不是抢占式的。循环中的 goroutine 永远不会进入调度程序,因此永远不会被杀死。 @jimt 我希望我可以否决您的评论(尽管,+1 表示有用的答案)。 Elazar 所说的有一些优点,尽管措辞可能有误:我们如何才能停止一个忙碌的 goroutine,这就是问题所在。当可以完美地解释 go 的并发模型时,为什么要摆脱某人,杰里米的回答让我非常清楚。当对方要求解释时,不需要“有魅力”的答案。【参考方案2】:

编辑: 在意识到您的问题是关于向 goroutine 中的 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()

【讨论】:

内部 goroutine 的主体更习惯使用defer 来调用wg.Done(),并使用range ch 循环遍历所有值直到通道关闭。【参考方案3】:

你不能从外面杀死一个 goroutine。您可以向 goroutine 发出信号以停止使用通道,但 goroutine 无法进行任何类型的元管理。 Goroutines 旨在合作解决问题,因此杀死一个行为不端的程序几乎永远不会是一个适当的响应。如果您想要隔离以实现健壮性,您可能需要一个进程。

【讨论】:

你可能想看看 encoding/gob 包,它可以让两个 Go 程序通过管道轻松交换数据结构。 就我而言,我有一个 goroutine 将在系统调用中被阻塞,我需要告诉它中止系统调用然后退出。如果我在阅读频道时被阻止,可以按照您的建议进行操作。 我以前看到过这个问题。我们“解决”它的方式是在应用程序启动时增加线程数,以匹配可能的 goroutine 数量 + CPU 数量【参考方案4】:

一般来说,你可以在 goroutine 中创建一个通道并接收一个停止信号。

本例中有两种创建频道的方法。

    频道

    上下文。在示例中我将演示context.WithCancel

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

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

【讨论】:

这正是我要找的!【参考方案5】:

我知道这个答案已经被接受,但我想我会投入 2cents。我喜欢使用 tomb 包。它基本上是一个升级的退出通道,但它也可以做一些不错的事情,比如传回任何错误。受控制的例程仍然负责检查远程终止信号。 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

【讨论】:

这个包挺有意思的!你有没有测试过 tomb 对 goroutine 做了什么,以防它内部发生某些事情导致恐慌,例如?从技术上讲,goroutine 在这种情况下会退出,所以我假设它仍然会调用延迟的 proc.Tomb.Done()... 嗨,格温妮丝,是的,proc.Tomb.Done() 会在程序崩溃之前执行,但目的是什么?主 goroutine 可能有一个非常小的机会窗口来执行某些语句,但它无法从另一个 goroutine 的恐慌中恢复,所以程序仍然崩溃。文档说:“当函数 F 调用 panic 时,F 的执行停止,F 中的任何延迟函数都正常执行,然后 F 返回到它的调用者..进程继续向上堆栈,直到当前 goroutine 中的所有函数都返回,此时程序崩溃。” 这是一个很好的答案 - 比在 some cases 中使用上下文更好。 API 与tomb.v2 的用法略有不同但相似。【参考方案6】:

就个人而言,我想在 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 写了一篇很棒的帖子:http://dave.cheney.net/2013/04/30/curious-channels。

【讨论】:

那真是太美了。【参考方案7】:

我将提供一种与此处提供的方法略有不同的方法。

我将假设需要停止的goroutine 正在执行一些与其他goroutines 完全无关的工作。该工作将由default select case 代表:

default:
    fmt.Println("working")
    time.Sleep(1 * time.Second)

另一个goroutine(在我的示例中是main)决定它应该停止正在执行某些工作的goroutine。你不能真正杀死goroutine。即使可以,这也是一个坏主意,因为它可能会使goroutine 处于不希望的状态。因此,我们必须使用通道来传达有人正在向goroutine 发出信号以停止。

stop := make(chan struct)

由于goroutine 将不断执行一些工作。我们将使用一个循环来表示它。而当停止信号发出时,goroutine 就会跳出循环。

go func() 
L:
    for 
        select 
        case <-stop:
            fmt.Println("stopping")
            break L
        default:
            fmt.Println("working")
            time.Sleep(1 * time.Second)
        
    
()

我们可以使用另一个通道向main 表明goroutine 已经停止。这是完整的示例:

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

main 线程产生一个goroutine 来执行一些工作一段时间(在本例中为 5 秒)。当时间到时,它会向goroutine 发送一个停止信号并等待它,直到goroutine 完全停止。

【讨论】:

以上是关于如何停止一个 goroutine的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Golang中的Context包

深入理解Golang中的Context包

ORACLE如何停止一个JOB

如何正确停止线程

Android如何停止线程的方式

Golang 并发编程指南