如何停止一个 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的主要内容,如果未能解决你的问题,请参考以下文章