[英]Golang pattern to kill multiple goroutines at once
I have two goroutines as shown in the snippet below.我有两个 goroutine,如下面的代码片段所示。 I want to synchronize them such that when one returns, the other one should also exit.
我想同步它们,这样当一个返回时,另一个也应该退出。 What is best way in go to achieve this?
go 中实现此目标的最佳方法是什么?
func main() {
go func() {
...
if err != nil {
return
}
}()
go func() {
...
if err != nil {
return
}
}()
}
I have simulated this scenario here https://play.golang.org/p/IqawStXt7rt and tried to solve it with a channel to signal a routine is done.我在这里模拟了这种情况https://play.golang.org/p/IqawStXt7rt并尝试用一个通道来解决它,以表示例程已完成。 This looks like there can be a write to closed channel resulting in a panic.
这看起来可能有写入关闭的通道导致恐慌。 What is the best way to solve this problem?
解决这个问题的最佳方法是什么?
You can use context for communication between two go routines.您可以使用上下文在两个 go 例程之间进行通信。 For example,
例如,
package main
import (
"context"
"sync"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg := sync.WaitGroup{}
wg.Add(3)
go func() {
defer wg.Done()
for {
select {
// msg from other goroutine finish
case <-ctx.Done():
// end
}
}
}()
go func() {
defer wg.Done()
for {
select {
// msg from other goroutine finish
case <-ctx.Done():
// end
}
}
}()
go func() {
defer wg.Done()
// your operation
// call cancel when this goroutine ends
cancel()
}()
wg.Wait()
}
Use close on a channel to signal completion.在通道上使用 close 表示完成。 This allows multiple goroutines to check for completion by receiving on the channel.
这允许多个 goroutine 通过在通道上接收来检查完成情况。
Use one channel per goroutine to signal completion of the goroutine.每个 goroutine 使用一个通道来表示 goroutine 的完成。
done1 := make(chan struct{}) // closed when goroutine 1 returns
done2 := make(chan struct{}) // closed when goroutine 2 returns
go func() {
defer close(done1)
timer1 := time.NewTicker(1 * time.Second)
defer timer1.Stop()
timer2 := time.NewTicker(2 * time.Second)
defer timer2.Stop()
for {
select {
case <-done2:
// The other goroutine returned.
fmt.Println("done func 1")
return
case <-timer1.C:
fmt.Println("timer1 func 1")
case <-timer2.C:
fmt.Println("timer2 func 1")
return
}
}
}()
go func() {
defer close(done2)
for {
select {
case <-done1:
// The other goroutine returned.
fmt.Println("done func 2")
return
default:
time.Sleep(3 * time.Second)
fmt.Println("sleep done from func 2")
return
}
}
}()
fmt.Println("waiting for goroutines to complete")
// Wait for both goroutines to return. The order that
// we wait here does not matter.
<-done1
<-done2
fmt.Println("all done")
First separate the waiting on go-routines and the done
channel.首先将等待 go-routines 和
done
通道分开。
Use a sync.WaitGroup
to coordinate the goroutines.使用
sync.WaitGroup
来协调 goroutines。
func main() {
wait := &sync.WaitGroup{}
N := 3
wait.Add(N)
for i := 1; i <= N; i++ {
go goFunc(wait, i, true)
}
wait.Wait()
fmt.Println(`Exiting main`)
}
Each goroutine will look like this:每个 goroutine 将如下所示:
// code for the actual goroutine
func goFunc(wait *sync.WaitGroup, i int, closer bool) {
defer wait.Done()
defer fmt.Println(`Exiting `, i)
T := time.Tick(time.Duration(100*i) * time.Millisecond)
for {
select {
case <-T:
fmt.Println(`Tick `, i)
if closer {
return
}
}
}
}
( https://play.golang.org/p/mDO4P56lzBU ) ( https://play.golang.org/p/mDO4P56lzBU )
Our main func is successfully waiting for the goroutines to exit before it exits.我们的 main 函数在退出之前成功地等待 goroutines 退出。 Each goroutine is closing itself, and we want a way to cancel all our goroutines at the same time.
每个 goroutine 都在关闭自己,我们想要一种同时取消所有 goroutine 的方法。
We'll do this with a chan
, and make use of this feature of receiving from channels:我们将使用
chan
来执行此操作,并利用从频道接收的这一特性:
QUOTE: A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.引用:关闭通道上的接收操作总是可以立即进行,在接收到任何先前发送的值之后产生元素类型的零值。 ( https://golang.org/ref/spec#Receive_operator )
( https://golang.org/ref/spec#Receive_operator )
We modify our goroutines to check for a CLOSE:我们修改我们的 goroutine 来检查 CLOSE:
func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
defer wait.Done()
defer fmt.Println(`Exiting `, i)
T := time.Tick(time.Duration(100*i) * time.Millisecond)
for {
select {
case <-CLOSE:
return
case <-T:
fmt.Println(`Tick `, i)
if closer {
close(CLOSE)
}
}
}
}
and then we change our func main
so that it passes the CLOSE channel through, and we'll set the closer
variable so that only the last of our goroutines will trigger the close:然后我们改变我们的
func main
让它通过 CLOSE 通道,我们将设置closer
变量,这样只有最后一个 goroutine 会触发关闭:
func main() {
wait := &sync.WaitGroup{}
N := 3
CLOSE := make(chan struct{})
// Launch the goroutines
wait.Add(N)
for i := 1; i <= N; i++ {
go goFunc(wait, i, i == N, CLOSE)
}
// Wait for the goroutines to finish
wait.Wait()
fmt.Println(`Exiting main`)
}
( https://play.golang.org/p/E91CtRAHDp2 ) ( https://play.golang.org/p/E91CtRAHDp2 )
Now it looks like everything is working.现在看起来一切正常。
But it isn't.但事实并非如此。 Concurrency is hard.
并发很难。 There's a bug lurking in this code, just waiting to bite you in production.
这段代码中潜伏着一个错误,正等着在生产中咬你。 Let's surface it.
让我们浮出水面。
Change our example so that every goroutine will close:更改我们的示例,以便每个goroutine 都将关闭:
func main() {
wait := &sync.WaitGroup{}
N := 3
CLOSE := make(chan struct{})
// Launch the goroutines
wait.Add(N)
for i := 1; i <= N; i++ {
go goFunc(wait, i, true /*** EVERY GOROUTINE WILL CLOSE ***/, CLOSE)
}
// Wait for the goroutines to finish
wait.Wait()
fmt.Println(`Exiting main`)
}
Change goroutine so that it takes a while before closing.更改 goroutine 以便在关闭之前需要一段时间。 We want two goroutines to be about to close at the same time:
我们希望两个 goroutine 同时关闭:
// code for the actual goroutine
func goFunc(wait *sync.WaitGroup, i int, closer bool, CLOSE chan struct{}) {
defer wait.Done()
defer fmt.Println(`Exiting `, i)
T := time.Tick(time.Duration(100*i) * time.Millisecond)
for {
select {
case <-CLOSE:
return
case <-T:
fmt.Println(`Tick `, i)
if closer {
/*** TAKE A WHILE BEFORE CLOSING ***/
time.Sleep(time.Second)
close(CLOSE)
}
}
}
}
( https://play.golang.org/p/YHnbDpnJCks ) ( https://play.golang.org/p/YHnbDpnJCks )
We crash with:我们崩溃:
Tick 1
Tick 2
Tick 3
Exiting 1
Exiting 2
panic: close of closed channel
goroutine 7 [running]:
main.goFunc(0x40e020, 0x2, 0x68601, 0x430080)
/tmp/sandbox558886627/prog.go:24 +0x2e0
created by main.main
/tmp/sandbox558886627/prog.go:38 +0xc0
Program exited: status 2.
While a receive on a closed channel returns immediately, you cannot close a closed channel.虽然关闭通道上的接收立即返回,但您无法关闭关闭的通道。
We need a little coordination.我们需要一点协调。 We can do this with a
sync.Mutex
and a bool
to indicate whether we've closed the channel or not.我们可以使用
sync.Mutex
和bool
来指示我们是否关闭了通道。 Let's create a struct to do this:让我们创建一个结构来执行此操作:
type Close struct {
C chan struct{}
l sync.Mutex
closed bool
}
func NewClose() *Close {
return &Close {
C: make(chan struct{}),
}
}
func (c *Close) Close() {
c.l.Lock()
if (!c.closed) {
c.closed=true
close(c.C)
}
c.l.Unlock()
}
Rewrite our gofunc and our main to use our new Close struct, and we're good to go: https://play.golang.org/p/eH3djHu8EXW重写我们的 gofunc 和我们的 main 以使用我们的新 Close 结构,我们对 go 很好: https://play.golang.org/p/eH3djHu8EXW
The problem with concurrency is that you always need to be wondering what would happen if another 'thread' was anywhere else in the code.并发的问题在于,您总是需要想知道如果另一个“线程”在代码中的其他任何地方会发生什么。
package main
import (
"fmt"
"sync"
"time"
)
func func1(done chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
timer1 := time.NewTicker(1 * time.Second)
timer2 := time.NewTicker(2 * time.Second)
for {
select {
case <-timer1.C:
fmt.Println("timer1 func 1")
case <-timer2.C:
// Ask GC to sweep the tickers timer1, timer2
// as goroutine should return
timer1.Stop()
timer2.Stop()
fmt.Println("timer2 func 1")
done <- struct{}{} // Signal the other goroutine to terminate
fmt.Println("sent done from func 1")
return
case <-done:
// Ask GC to sweep the tickers timer1, timer2
// as goroutine should return
timer1.Stop()
timer2.Stop()
fmt.Println("done func 1")
return
}
}
}
func func2(done chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
timer3 := time.NewTicker(3 * time.Second)
for {
select {
case <-timer3.C:
// Ask GC to sweep the tickers timer3
// as goroutine should return
timer3.Stop()
fmt.Println("timer3 func 2")
done <- struct{}{} // Signal the other goroutine to terminate
fmt.Println("sent done from func 2")
return
case <-done:
// Ask GC to sweep the tickers timer3
// as goroutine should return
timer3.Stop()
fmt.Println("done func 2")
return
}
}
}
func main() {
// Chan used for signalling between goroutines
done := make(chan struct{})
// WaitGroup
wg := sync.WaitGroup{}
wg.Add(2)
// Spawn the goroutine for func1
go func1(done, &wg)
// Spawn the goroutine for func2
go func2(done, &wg)
fmt.Println("starting sleep")
// Wait for the goroutines
wg.Wait()
// Wait for 15 seconds
// If not required, please remove
// the lines below
time.Sleep(15 * time.Second)
fmt.Println("waited 15 seconds")
}
Your problem is that you want a single send on the DONE channel to be received by multiple listeners.您的问题是您希望 DONE 通道上的单个发送被多个侦听器接收。 You also need to consider whether a send on the
done
channel is received by your goroutines, or by your main
func.您还需要考虑您的 goroutine 或您的
main
func 是否接收到done
通道上的发送。
I suggest you rather separate the waiting on go-routines and the done
channel.我建议您将等待 go-routines 和
done
通道分开。
import `sync`
// This code will wait for the two functions to complete before ending
func main {
var wait sync.WaitGroup
wait.Add(2)
go func() {
defer wait.Done()
}()
go g() {
defer wait.Done()
}()
wait.Wait()
}
Now, how to manage the Done.现在,如何管理完成。 Well, the solution is to use a
sync.Cond
and have each goroutine run its own goroutine to wait on the Cond.好吧,解决方案是使用
sync.Cond
并让每个 goroutine 运行自己的 goroutine 以等待 Cond。 Here's an example:这是一个例子:
package main
import (
`fmt`
`sync`
`time`
)
// WaitForIt wraps a Cond and a Mutex for a simpler API:
// .WAIT() chan struct{} will return a channel that will be
// signalled when the WaitForIt is done.
// .Done() will indicate that the WaitForIt is done.
type WaitForIt struct {
L *sync.Mutex
Cond *sync.Cond
}
func NewWaitForIt() *WaitForIt {
l := &sync.Mutex{}
c := sync.NewCond(l)
return &WaitForIt{ l, c }
}
// WAIT returns a chan that will be signalled when
// the Cond is triggered.
func (w *WaitForIt) WAIT() chan struct{} {
D := make(chan struct{})
go func() {
w.L.Lock()
defer w.L.Unlock()
w.Cond.Wait()
D <- struct{}{}
close(D)
}()
return D
}
// Done indicates that the Cond should be triggered.
func (w *WaitForIt) Done() {
w.Cond.Broadcast()
}
// doneFunc launches the func f with a chan that will be signalled when the
// func should stop. It also handles WaitGroup synchronization
func doneFunc(wait *sync.WaitGroup, waitForIt *WaitForIt, f func(DONE chan struct{})) {
defer wait.Done()
f(waitForIt.WAIT())
}
func main() {
// wait will coordinate all the goroutines at the level of main()
// between themselves the waitForIt will do the coordination
wait := &sync.WaitGroup{}
// waitForIt indicates to the goroutines when they should shut
waitForIt := NewWaitForIt()
// goFunc generates each goroutine. Only the 3-second goroutine will
// shutdown all goroutines
goFunc := func(seconds int) func(chan struct{}) {
return func(DONE chan struct{}) {
// this is the actual code of each goroutine
// it makes a ticker for a number of seconds,
// and prints the seconds after the ticker elapses,
// or exits if DONE is triggered
timer := time.NewTicker(time.Duration(seconds) * time.Second)
defer timer.Stop()
for {
select {
case <- DONE:
return
case <- timer.C:
if (3==seconds) {
waitForIt.Done()
// Don't shutdown here - we'll shutdown
// when our DONE is signalled
}
}
}
}
}
// launch 3 goroutines, each waiting on a shutdown signal
for i:=1; i<=3; i++ {
wait.Add(1)
go doneFunc(wait, waitForIt, goFunc(i))
}
// wait for all the goroutines to complete, and we're done
wait.Wait()
}
Here's your example implemented using WaitForIt: https://play.golang.org/p/llphW73G1xE Note that I had to remove the Lock()
call in WaitForIt.Done
.这是您使用 WaitForIt 实现的示例: https://play.golang.org/p/llphW73G1xE请注意,我必须删除
WaitForIt.Done
中的Lock()
调用。 Although the documentation says you're allowed to hold the lock, it was blocking your 2nd goroutine from completing.尽管文档说您可以持有锁,但它阻止了您的第二个 goroutine 完成。
You could use channel closing pattern to wait within multiple go routines.您可以使用通道关闭模式在多个 go 例程中等待。
package main
import (
"os"
"os/signal"
"sync"
"syscall"
)
type RunGroup struct {
sync.WaitGroup
}
// Run handles wait group state
func (runGroup *RunGroup) Run(f func()) {
runGroup.Add(1)
go func() {
f()
runGroup.Done()
}()
}
func doStuff(done <-chan any, id int) {
println("Doing something", id)
<-done
println("DONE", id)
}
func main() {
// Done channel
done := make(chan any)
// Setup Shutdown listeners
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM)
signal.Notify(sigChan, syscall.SIGINT)
go func() {
rawSig := <-sigChan
sig := rawSig.String()
println("Caught signal, shutting down.", sig)
close(done)
}()
runGroup := RunGroup{}
// Do some stuff
runGroup.Run(func () {
doStuff(done, 1)
})
runGroup.Run(func () {
doStuff(done, 2)
})
runGroup.Run(func () {
doStuff(done, 3)
})
// Wait mainthread until interrupt
runGroup.Wait()
}
Output Output
go run ./main.go
Doing something 3
Doing something 2
Doing something 1
^CCaught signal, shutting down. interrupt
DONE 3
DONE 1
DONE 2
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.