如何停止一个 Goroutine

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

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

我怎么才能阻止这样的事情发生呢?

193198 次浏览

通常,您将一个(可能是单独的)信号通道传递给 goroutine。该信号通道用于在您希望 goroutine 停止时将一个值压入。常规民意调查定期进行。一旦检测到信号,它就会退出。

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


// Do stuff


// Quit goroutine
quit <- true

你不能从外面杀一只猩猩。您可以通知 goroutine 停止使用通道,但是 goroutine 没有处理任何类型的元管理。Goroutines 的目的是合作解决问题,所以杀死一个行为不端的人几乎永远不会是一个适当的反应。如果需要为了健壮性而进行隔离,则可能需要一个进程。

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

如果您的 goroutine 存在的唯一目的是处理出来的项目禅,您可以利用“关闭”内置和通道的特殊接收形式。

也就是说,一旦您完成了在 chan 上发送项目,就关闭它。然后,在 goroutine 中,接收操作符获得一个额外的参数,用于显示通道是否已关闭。

下面是一个完整的示例(使用 wait 组确保流程继续下去,直到 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()
}

我知道这个答案已经被接受了,但是我想我应该加入进来。我喜欢使用 坟墓软件包。它基本上是一个假想的退出通道,但它也可以做很多事情,比如传回任何错误。受控的例程仍然负责检查远程终止信号。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

就个人而言,我喜欢在 Goroutine 频道中使用 range:

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

通常,您可以在 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")
}

我将提供一种与这里提供的方法略有不同的方法。

我将假设需要停止的 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完全停止。

我修改了邹英的答案,做了一个可停止的计时器,希望能帮到别人。

func main() {


timeOut := make(chan bool)
stopTimer := false


//10 sec timer - go-routine
go func() {
var counter = 0
for {
if stopTimer{
timeOut <- false
return
}else {
fmt.Printf("timer: %d sec\n", counter)
time.Sleep(1 * time.Second)
counter++
if counter >= 10 {
timeOut <- true
return
}
}
}
}()




go func() {
time.Sleep(8 * time.Second) //change 8 to 11 or more to time-out the timer
stopTimer = true
}()


b := <-timeOut
fmt.Printf("time-out ,%v\n", b)


}