如何停止一个 goroutine

huangapple go评论85阅读模式
英文:

How to stop a goroutine

问题

我有一个goroutine调用一个方法,并将返回值传递到一个通道中:

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

如何停止这样的goroutine?

英文:

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

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

How do I stop such a goroutine?

答案1

得分: 162

通常情况下,你会传递一个(可能是独立的)信号通道给goroutine。这个信号通道用于在你想要goroutine停止时推送一个值进去。goroutine会定期轮询该通道。一旦它检测到一个信号,它就会退出。

quit := make(chan bool)
go func() {
    for {
        select {
        case <- quit:
            return
        default:
            // 做其他的事情
        }
    }
}()

// 做其他的事情

// 停止goroutine
quit <- true
英文:

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 &lt;- quit:
            return
        default:
            // Do other stuff
        }
    }
}()

// Do stuff

// Quit goroutine
quit &lt;- true

答案2

得分: 66

如果你的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()
}
英文:

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 &quot;sync&quot;
func main() {
	var wg sync.WaitGroup
	wg.Add(1)

	ch := make(chan int)
	go func() {
		for {
			foo, ok := &lt;- ch
			if !ok {
				println(&quot;done&quot;)
				wg.Done()
				return
			}
			println(foo)
		}
	}()
	ch &lt;- 1
	ch &lt;- 2
	ch &lt;- 3
	close(ch)

	wg.Wait()
}

答案3

得分: 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")
}
英文:

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 &quot;fmt&quot;
import &quot;time&quot;

func do_stuff() int {
	return 1
}

func main() {

	ch := make(chan int, 100)
	done := make(chan struct{})
	go func() {
		for {
			select {
			case ch &lt;- do_stuff():
			case &lt;-done:
				close(ch)
				return
			}
			time.Sleep(100 * time.Millisecond)
		}
	}()

	go func() {
		time.Sleep(3 * time.Second)
		done &lt;- struct{}{}
	}()

	for i := range ch {
		fmt.Println(&quot;receive value: &quot;, i)
	}

	fmt.Println(&quot;finish&quot;)
}

The second demo, use context:

package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	forever := make(chan struct{})
	ctx, cancel := context.WithCancel(context.Background())

	go func(ctx context.Context) {
		for {
			select {
			case &lt;-ctx.Done():  // if cancel() execute
				forever &lt;- struct{}{}
                return
			default:
				fmt.Println(&quot;for loop&quot;)
			}

			time.Sleep(500 * time.Millisecond)
		}
	}(ctx)

	go func() {
		time.Sleep(3 * time.Second)
		cancel()
	}()

	&lt;-forever
	fmt.Println(&quot;finish&quot;)
}

答案4

得分: 49

你无法从外部杀死一个 goroutine。你可以使用通道来发送信号给 goroutine 停止运行,但是没有办法对 goroutine 进行任何元管理操作。Goroutine 的设计初衷是协作解决问题,因此杀死一个行为不当的 goroutine 几乎永远不会是一个合适的响应。如果你想要隔离以提高稳定性,可能需要使用一个进程。

英文:

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.

答案5

得分: 14

我知道这个答案已经被接受了,但我想加入我的意见。我喜欢使用tomb包。它基本上是一个增强版的quit channel,但它还可以将任何错误传递回来。控制下的例程仍然有责任检查远程终止信号。据我所知,如果一个goroutine表现不正常(即陷入无限循环),是不可能获取它的“id”并杀死它的。

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

package main

import (
  "launchpad.net/tomb"
  "time"
  "fmt"
)

type Proc struct {
  Tomb tomb.Tomb
}

func (proc *Proc) Exec() {
  defer proc.Tomb.Done() // 只能调用一次
  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() // 将返回杀死proc的错误
  fmt.Println(err)
}

输出应该如下所示:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
英文:

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 (
  &quot;launchpad.net/tomb&quot;
  &quot;time&quot;
  &quot;fmt&quot;
)

type Proc struct {
  Tomb tomb.Tomb
}

func (proc *Proc) Exec() {
  defer proc.Tomb.Done() // Must call only once
  for {
    select {
    case &lt;-proc.Tomb.Dying():
      return
    default:
      time.Sleep(300 * time.Millisecond)
      fmt.Println(&quot;Loop the loop&quot;)
    }
  }
}

func main() {
  proc := &amp;Proc{}
  go proc.Exec()
  time.Sleep(1 * time.Second)
  proc.Tomb.Kill(fmt.Errorf(&quot;Death from above&quot;))
  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

答案6

得分: 11

个人而言,我喜欢在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.

英文:

Personally, I'd like to use range on a channel in a goroutine:

https://play.golang.org/p/qt48vvDu8cd

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
)

func main() {
	var wg sync.WaitGroup
	c := make(chan bool)
	wg.Add(1)
	go func() {
		defer wg.Done()
		for b := range c {
			fmt.Printf(&quot;Hello %t\n&quot;, b)
		}
	}()
	c &lt;- true
	c &lt;- true
	close(c)
	wg.Wait()
}

Dave has written a great post about this: http://dave.cheney.net/2013/04/30/curious-channels.

答案7

得分: 0

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

我假设需要停止的goroutine执行的工作与其他goroutine没有任何关系。这个工作将由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)
        }
    }
}()

我们可以使用另一个通道来指示maingoroutine已经停止。以下是完整的示例:

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{}{} // 发送停止信号
    close(stop)
    <-stopped // 等待停止
}

main线程生成一个goroutine来执行一些工作一段时间(在这种情况下为5秒)。当时间到期时,它向goroutine发送停止信号,并等待直到goroutine完全停止。

英文:

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(&quot;working&quot;)
    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 &lt;-stop:
			fmt.Println(&quot;stopping&quot;)
			break L
		default:
			fmt.Println(&quot;working&quot;)
			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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	stop := make(chan struct{})
	stopped := make(chan struct{})

	go func() {
	L:
		for {
			select {
			case &lt;-stop:
				fmt.Println(&quot;stopping&quot;)
				break L
			default:
				fmt.Println(&quot;working&quot;)
				time.Sleep(1 * time.Second)
			}
		}

		fmt.Println(&quot;stopped&quot;)
		stopped &lt;- struct{}{}
	}()

	&lt;-time.After(5 * time.Second)
	stop &lt;- struct{}{} // send a signal to stop
	close(stop)
	&lt;-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.

答案8

得分: 0

我执行以下操作,使用close(quitCh)。使用close()将向所有监听通道广播退出信号,在这种情况下,所有的go例程都在监听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, "退出")
			return
		case <-ticker.C:
			fmt.Println(id, "做你的事情")
		}
	}

}

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("等待中")
	wg.Wait()

	fmt.Println("完成")

}
英文:

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 (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

func routine(ch chan struct{}, wg *sync.WaitGroup, id int) {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case &lt;-ch:
			wg.Done()
			fmt.Println(id, &quot;quiting&quot;)
			return
		case &lt;-ticker.C:
			fmt.Println(id, &quot;do your stuff&quot;)
		}
	}

}

func main() {

	var wg sync.WaitGroup

	c := make(chan struct{}, 1)
	for i := 0; i &lt; 3; i++ {
		wg.Add(1)
		go routine(c, &amp;wg, i)
	}

	&lt;-time.After(time.Second * 2)
	close(c)
	fmt.Println(&quot;waiting&quot;)
	wg.Wait()

	fmt.Println(&quot;Done&quot;)

}
 

huangapple
  • 本文由 发表于 2011年7月24日 23:05:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/6807590.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定